diff --git a/add-a-relay.mdx b/add-a-relay.mdx
index 2460798..3ba33c3 100644
--- a/add-a-relay.mdx
+++ b/add-a-relay.mdx
@@ -7,6 +7,12 @@ import RelayEndpointConfig from '/snippets/relay-endpoint-config.mdx';
By default, iroh endpoints use the public relays maintained by [n0.computer](https://n0.computer) to facilitate connections when direct peer-to-peer links aren't possible. The public relays are great for development and testing, but production deployments should run their own.
+## Configure your endpoint
+
+Once you have one or more relay URLs, configure your endpoint to use them:
+
+
+
## Why use your own relay?
Running dedicated relays gives you:
@@ -39,11 +45,6 @@ You have two paths. Pick **managed** if you want a relay running today without o
Run the `iroh-relay` binary on a server with a public IP and DNS name. Automatic TLS via ACME is built in.
-## Configure your endpoint
-
-Once you have one or more relay URLs, configure your endpoint to use them:
-
-
## Recommended setup
diff --git a/connect-two-endpoints.mdx b/connect-two-endpoints.mdx
index eb0506b..c8a044d 100644
--- a/connect-two-endpoints.mdx
+++ b/connect-two-endpoints.mdx
@@ -9,23 +9,51 @@ The full example can be [viewed on GitHub](https://github.com/n0-computer/iroh-p
## Set up
-We'll assume you've set up [Rust](https://www.rust-lang.org/) and [cargo](https://doc.rust-lang.org/cargo/) on your machine.
+Pick your language and create a project with the iroh bindings installed. Each tab assumes you already have the language toolchain on your machine. For full setup details per language (Xcode project for Swift, NDK targets for Kotlin/Android, etc.) see the [language guides](/languages/index).
-Create a new project:
+
-```bash
+```bash Rust
cargo init ping-pong
cd ping-pong
+cargo add iroh iroh-ping iroh-tickets anyhow tracing-subscriber
+cargo add tokio --features full
+# work in src/main.rs
```
-Add the dependencies:
+```bash Python
+mkdir ping-pong && cd ping-pong
+python -m venv .venv && source .venv/bin/activate
+pip install iroh
+# work in main.py
+```
-```bash
-cargo add iroh iroh-ping iroh-tickets anyhow tracing-subscriber
-cargo add tokio --features full
+```bash Swift
+swift package init --type executable --name ping-pong
+cd ping-pong
+# add to Package.swift dependencies:
+# .package(url: "https://github.com/n0-computer/iroh-ffi", from: "1.0.0-rc.0")
+# and to your target dependencies:
+# .product(name: "IrohLib", package: "iroh-ffi")
+# work in Sources/ping-pong/main.swift
```
-From here on we'll be working in `src/main.rs`.
+```bash Kotlin
+# scaffold a Gradle project, then add the iroh dependency:
+# dependencies { implementation("computer.iroh:iroh:1.0.0-rc.0") }
+# (Maven Central — requires mavenCentral() in your repositories block)
+# work in a Main.kt with `fun main(...)`
+```
+
+```bash JavaScript
+mkdir ping-pong && cd ping-pong
+npm init -y
+npm pkg set type=module
+npm install @number0/iroh
+# work in main.mjs
+```
+
+
## Protocols and ALPN
@@ -51,9 +79,11 @@ For more on how this works, see [Tickets](/concepts/tickets) and [Discovery](/co
## The receiver
-The receiver creates an iroh endpoint, brings it online, prints a ticket containing its address, and runs a router that accepts incoming ping requests until you press Ctrl+C:
+The receiver creates an iroh endpoint, brings it online, prints a ticket containing its address, and accepts incoming ping requests until you press Ctrl+C. The Rust version uses the `iroh-ping` protocol crate; the other languages open the bidirectional stream by hand and echo the payload back, since iroh-ping isn't bound yet.
-```rust
+
+
+```rust Rust
use anyhow::Result;
use iroh::{Endpoint, endpoint::presets, protocol::Router};
use iroh_ping::Ping;
@@ -75,11 +105,80 @@ async fn run_receiver() -> Result<()> {
}
```
+```python Python
+async def receiver():
+ iroh.iroh_ffi.uniffi_set_event_loop(asyncio.get_running_loop())
+ ep = await iroh.Endpoint.bind(iroh.EndpointOptions(alpns=[list(ALPN)]))
+ print("ticket:", str(iroh.EndpointTicket(ep.addr())))
+
+ incoming = await ep.accept_next()
+ conn = await (await incoming.accept()).connect()
+ bi = await conn.accept_bi()
+ msg = await bi.recv().read_to_end(1024)
+ await bi.send().write_all(msg)
+ await bi.send().finish()
+ await ep.close()
+```
+
+```swift Swift
+func receiver() async throws {
+ let ep = try await Endpoint.bind(
+ options: EndpointOptions(preset: presetN0(), alpns: [ALPN])
+ )
+ let ticket = try EndpointTicket(addr: ep.addr())
+ print("ticket: \(ticket)")
+
+ let incoming = try await ep.acceptNext()!
+ let conn = try await incoming.accept().connect()
+ let bi = try await conn.acceptBi()
+ let msg = try await bi.recv().readToEnd(sizeLimit: 1024)
+ try await bi.send().writeAll(buf: msg)
+ try await bi.send().finish()
+ try await ep.close()
+}
+```
+
+```kotlin Kotlin
+suspend fun receiver() {
+ val ep = Endpoint.bind(
+ EndpointOptions(preset = presetN0(), alpns = listOf(ALPN)),
+ )
+ println("ticket: ${EndpointTicket(ep.addr())}")
+
+ val incoming = ep.acceptNext()!!
+ val conn = incoming.accept().connect()
+ val bi = conn.acceptBi()
+ val msg = bi.recv().readToEnd(1024u)
+ bi.send().writeAll(msg)
+ bi.send().finish()
+ ep.shutdown()
+}
+```
+
+```javascript JavaScript
+async function receiver() {
+ const ep = await Endpoint.bind({ alpns: [ALPN] })
+ console.log('ticket:', EndpointTicket.fromAddr(ep.addr()).toString())
+
+ const incoming = await ep.acceptNext()
+ const conn = await (await incoming.accept()).connect()
+ const bi = await conn.acceptBi()
+ const msg = await bi.recv.readToEnd(1024)
+ await bi.send.writeAll(msg)
+ await bi.send.finish()
+ await ep.close()
+}
+```
+
+
+
## The sender
-The sender creates its own endpoint, parses the receiver's ticket, and dials over iroh-ping:
+The sender creates its own endpoint, parses the receiver's ticket, and dials. Rust delegates to `iroh-ping`; the other languages open a bidirectional stream, write `hello`, await the echo, and time the round trip.
-```rust
+
+
+```rust Rust
async fn run_sender(ticket: EndpointTicket) -> Result<()> {
let send_ep = Endpoint::bind(presets::N0).await?;
let send_pinger = Ping::new();
@@ -91,11 +190,81 @@ async fn run_sender(ticket: EndpointTicket) -> Result<()> {
}
```
+```python Python
+async def sender(ticket_str):
+ iroh.iroh_ffi.uniffi_set_event_loop(asyncio.get_running_loop())
+ ep = await iroh.Endpoint.bind(iroh.EndpointOptions())
+ addr = iroh.EndpointTicket.parse(ticket_str).endpoint_addr()
+ conn = await ep.connect(addr, list(ALPN))
+
+ bi = await conn.open_bi()
+ start = time.monotonic()
+ await bi.send().write_all(list(b"hello"))
+ await bi.send().finish()
+ await bi.recv().read_to_end(1024)
+ print(f"ping took: {(time.monotonic() - start) * 1000:.2f} ms")
+ await ep.close()
+```
+
+```swift Swift
+func sender(_ ticketStr: String) async throws {
+ let ep = try await Endpoint.bind(options: EndpointOptions(preset: presetN0()))
+ let addr = try EndpointTicket.parse(str: ticketStr).endpointAddr()
+ let conn = try await ep.connect(addr: addr, alpn: ALPN)
+
+ let bi = try await conn.openBi()
+ let start = Date()
+ try await bi.send().writeAll(buf: Data("hello".utf8))
+ try await bi.send().finish()
+ _ = try await bi.recv().readToEnd(sizeLimit: 1024)
+ let ms = Date().timeIntervalSince(start) * 1000
+ print(String(format: "ping took: %.2f ms", ms))
+ try await ep.close()
+}
+```
+
+```kotlin Kotlin
+suspend fun sender(ticketStr: String) {
+ val ep = Endpoint.bind(EndpointOptions(preset = presetN0()))
+ val addr = EndpointTicket.parse(ticketStr).endpointAddr()
+ val conn = ep.connect(addr, ALPN)
+
+ val bi = conn.openBi()
+ val ms = measureTimeMillis {
+ bi.send().writeAll("hello".toByteArray())
+ bi.send().finish()
+ bi.recv().readToEnd(1024u)
+ }
+ println("ping took: $ms ms")
+ ep.shutdown()
+}
+```
+
+```javascript JavaScript
+async function sender(ticketStr) {
+ const ep = await Endpoint.bind()
+ const addr = EndpointTicket.fromString(ticketStr).endpointAddr()
+ const conn = await ep.connect(addr, ALPN)
+
+ const bi = await conn.openBi()
+ const start = performance.now()
+ await bi.send.writeAll(Array.from(Buffer.from('hello')))
+ await bi.send.finish()
+ await bi.recv.readToEnd(1024)
+ console.log(`ping took: ${(performance.now() - start).toFixed(2)} ms`)
+ await ep.close()
+}
+```
+
+
+
## Wiring main
-Parse the command-line argument to decide whether to run as receiver or sender:
+Parse the command-line argument to decide whether to run as receiver or sender. Each language tab is a complete file — drop the receiver and sender from the previous sections above the entry point.
-```rust
+
+
+```rust Rust
use std::env;
use anyhow::anyhow;
@@ -122,21 +291,120 @@ async fn main() -> Result<()> {
}
```
+```python Python
+import asyncio
+import sys
+import time
+
+import iroh
+
+ALPN = b"iroh-tutorial/ping/0"
+
+# ... receiver() and sender() from the sections above ...
+
+if __name__ == "__main__":
+ if sys.argv[1] == "receiver":
+ asyncio.run(receiver())
+ else:
+ asyncio.run(sender(sys.argv[2]))
+```
+
+```swift Swift
+import Foundation
+import IrohLib
+
+let ALPN = Data("iroh-tutorial/ping/0".utf8)
+
+// ... receiver() and sender() from the sections above ...
+
+let args = CommandLine.arguments
+switch args[1] {
+case "receiver": try await receiver()
+case "sender": try await sender(args[2])
+default: fatalError("usage: receiver | sender ")
+}
+```
+
+```kotlin Kotlin
+import computer.iroh.*
+import kotlinx.coroutines.runBlocking
+import kotlin.system.measureTimeMillis
+
+private val ALPN = "iroh-tutorial/ping/0".toByteArray()
+
+// ... receiver() and sender() from the sections above ...
+
+fun main(args: Array) = runBlocking {
+ when (args[0]) {
+ "receiver" -> receiver()
+ "sender" -> sender(args[1])
+ else -> error("usage: receiver | sender ")
+ }
+}
+```
+
+```javascript JavaScript
+import { Endpoint, EndpointTicket } from '@number0/iroh'
+
+const ALPN = Array.from(Buffer.from('iroh-tutorial/ping/0'))
+
+// ... receiver() and sender() from the sections above ...
+
+const [cmd, arg] = process.argv.slice(2)
+if (cmd === 'receiver') await receiver()
+else if (cmd === 'sender') await sender(arg)
+else console.error('usage: receiver | sender ')
+```
+
+
+
## Run it
-In one terminal, start the receiver:
+In one terminal, start the receiver. It will print a ticket. Copy that ticket and run the sender in another terminal — you should see the round-trip time printed.
-```bash
+
+
+```bash Rust
+# terminal 1
cargo run -- receiver
+
+# terminal 2
+cargo run -- sender
```
-It will print a ticket. Copy that ticket and run the sender in another terminal:
+```bash Python
+# terminal 1
+python main.py receiver
-```bash
-cargo run -- sender
+# terminal 2
+python main.py sender
+```
+
+```bash Swift
+# terminal 1
+swift run ping-pong receiver
+
+# terminal 2
+swift run ping-pong sender
+```
+
+```bash Kotlin
+# terminal 1
+./gradlew run --args="receiver"
+
+# terminal 2
+./gradlew run --args="sender "
+```
+
+```bash JavaScript
+# terminal 1
+node main.mjs receiver
+
+# terminal 2
+node main.mjs sender
```
-You should see the round-trip time printed by the sender.
+
**Connection issues?** If the sender can't reach the receiver, see the [troubleshooting guide](/troubleshooting) to enable detailed logging or use `iroh-doctor` to diagnose network problems.
diff --git a/deployment/other-languages.mdx b/deployment/other-languages.mdx
deleted file mode 100644
index de4e5e7..0000000
--- a/deployment/other-languages.mdx
+++ /dev/null
@@ -1,109 +0,0 @@
----
-title: "Language Bindings"
----
-
-
-While iroh is written in Rust, it can be used in many other languages and
-environments.
-
-
-## Language Bindings
-
-| Language | Status |
-|----------|--------|
-| [Rust](https://github.com/n0-computer/iroh) | Official |
-| Kotlin | [Contact us](mailto:support@iroh.computer) |
-| Swift | [Contact us](mailto:support@iroh.computer) |
-| Python | [Contact us](mailto:support@iroh.computer) |
-| [TypeScript](https://github.com/rayhanadev/iroh-ts) | Community |
-
-The [n0.computer](https://n0.computer) engineering team can help build and maintain production-grade
-bindings for your language. [Contact
-us](mailto:support@iroh.computer) discuss your needs.
-
-
-### Build Your Own Wrapper
-
-If you're comfortable with a little bit of Rust, you can write your own wrapper
-around iroh. This can be a small application-specific binary that exposes
-functionality over a local HTTP server or daemon, or a full FFI wrapper from
-Rust to your target language (Python, Go, etc.). Either way, this gives you:
-
-- Full control over the API surface you expose
-- The ability to tailor it to your specific use case
-- Type-safe bindings for your language (with FFI)
-- Can be called from any language (with an HTTP wrapper)
-
-While it's easy to get a first version working, **ongoing maintenance and
-testing is the hard part**. Iroh is under active development, and keeping
-wrappers up to date with new releases, testing across platforms, and handling
-edge cases takes sustained effort. The number0 team runs a testing lab for
-this purpose. If you need production-grade bindings, [contact
-us](https://cal.com/team/number-0/iroh-services) to discuss your requirements.
-
-### Community Bindings
-
-The community has built language bindings that are open source and available for use:
-
-**TypeScript/JavaScript**: [iroh-ts](https://github.com/rayhanadev/iroh-ts) - Community-maintained TypeScript bindings for iroh, enabling JavaScript and TypeScript developers to use iroh in their applications.
-
-
-## WebAssembly and Browsers
-
-Iroh can be compiled to WebAssembly for use in browsers!
-
-As of the iroh 0.33 release, iroh can be used in projects that compile to WebAssembly.
-Add `iroh` to your browser project's dependencies and keep building it using [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen/).
-
-For end-to-end examples of integrating iroh into a browser page, see these examples on our [iroh-examples repository](https://github.com/n0-computer/iroh-examples):
-- An [iroh echo server in the browser](https://github.com/n0-computer/iroh-examples/tree/main/browser-echo). Check it out [in action](https://n0-computer.github.io/iroh-examples/main/browser-echo/index.html)
-- An [browser chat room UI](https://github.com/n0-computer/iroh-examples/tree/main/browser-chat) based on the [iroh gossip chat example](/examples/chat). Check it out [in action](https://n0-computer.github.io/iroh-examples/main/browser-chat/index.html)
-
-### Browser Limitations
-
-When you're in a browser context - as opposed to in a native app or on desktop, you're in a strict "sandbox" that disallows a few things iroh relies upon, e.g. sending UDP packets directly.
-Thus, we need to work around these limitations and can't provide you with the full magic of iroh just yet.
-
-We envision that most applications will use iroh browser support as an additional feature to complement existing deployments to desktops, native apps or servers, where they'll be able to make use of everything that iroh offers.
-
-**No direct connections**: All connections from browsers to somewhere else need to flow via a relay server.
-This is because we can't port our hole-punching logic in iroh to browsers: They don't support sending UDP packets to IP addresses from inside the browser sandbox.
-
-Keep in mind that *connections are end-to-end encrypted*, as always with iroh.
-So even though traffic from browsers is always relayed, it can't be decrypted by the relay.
-
-There are other ways of getting direct connections going, such as WebTransport
-with `serverCertificateHashes`, or WebRTC. We may expand iroh's browser support
-to make use of these to try to generate direct connections even when a browser
-node is involved in the connection.
-
-**`iroh` crate features**: As of iroh version 0.33, you need to disable all optional features on iroh for the Wasm build to succeed.
-To do so, depend on iroh via `iroh = { version = "0.33", default-features = false }`.
-This will install a version of iroh with default features, except it doesn't track `metrics` locally.
-We'll get rid of this limitation very soon (likely with iroh version 0.34).
-Non-default features like `discovery-local-network` or `discovery-dht` will likely never be available in browsers, unless browser APIs making them possible are added.
-
-**npm package**: Currently we don't bundle iroh's Wasm build as an NPM package.
-There is no technical limitation for this: You could build this today!
-Should you need javascript APIs, we recommend that you write an application-specific rust wrapper crate that depends on iroh and exposes whatever the javascript side needs via [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen/).
-
-### Use in node.js/bun.js/deno
-
-We check that the browser version of iroh works in node.js regularly.
-And it's likely that Deno will work out of the box, too, given it closely resembles the browser's Web APIs.
-We haven't checked if bun.js works right now, and don't have plans to check that continually.
-
-As these runtimes are outside the browser sandbox, it would technically be possible to ship bigger parts of iroh to these environments, such as all hole-punching related functionality.
-However, we currently don't plan to expand the browser-related WebAssembly work to integrate with that.
-In the future, it's more likely we'll use [NAPI](https://napi.rs) or [WASI](https://wasi.dev/) to make these integrations possible.
-
-### Troubleshooting WebAssembly Builds
-
-Getting rust code to successfully build for browsers can be tricky.
-We've started a [discussion on github](https://github.com/n0-computer/iroh/discussions/3200) to collect common issues hit when trying to compile iroh or projects using iroh to WebAssembly.
-Take a look at answers in that discussion for any clues if you're encountering build issues.
-Otherwise feel free to open a discussion or a thread on that discussion with your specific issue.
-
-## Contributing
-
-If you're interested in creating or maintaining bindings for other languages, we'd love to hear from you! Join the [iroh Discord](https://www.iroh.computer/discord) to connect with the community.
diff --git a/docs.json b/docs.json
index f21ff59..e7904c6 100644
--- a/docs.json
+++ b/docs.json
@@ -16,7 +16,18 @@
"what-is-iroh",
"quickstart",
"compatibility",
- "deployment/other-languages",
+ {
+ "group": "Languages",
+ "expanded": false,
+ "pages": [
+ "languages/index",
+ "languages/rust",
+ "languages/python",
+ "languages/swift",
+ "languages/kotlin",
+ "languages/javascript"
+ ]
+ },
{
"group": "Tutorials",
"expanded": true,
@@ -117,7 +128,17 @@
"about/faq"
]
},
- "rust-api",
+ {
+ "group": "API References",
+ "expanded": false,
+ "pages": [
+ "rust-api",
+ "python-api",
+ "swift-api",
+ "kotlin-api",
+ "js-api"
+ ]
+ },
"examples",
"troubleshooting",
"iroh-services/support"
@@ -180,6 +201,10 @@
"source": "/examples/examples",
"destination": "/examples"
},
+ {
+ "source": "/deployment/other-languages",
+ "destination": "/languages"
+ },
{
"source": "/protocols/kv-crdts",
"destination": "/protocols/documents"
diff --git a/images/swift/other-ldflags-network.png b/images/swift/other-ldflags-network.png
new file mode 100644
index 0000000..a3049fe
Binary files /dev/null and b/images/swift/other-ldflags-network.png differ
diff --git a/images/swift/select-irohlib-product.png b/images/swift/select-irohlib-product.png
new file mode 100644
index 0000000..651972f
Binary files /dev/null and b/images/swift/select-irohlib-product.png differ
diff --git a/images/swift/signing-capabilities-network.png b/images/swift/signing-capabilities-network.png
new file mode 100644
index 0000000..766fa4c
Binary files /dev/null and b/images/swift/signing-capabilities-network.png differ
diff --git a/iroh-services/quickstart.mdx b/iroh-services/quickstart.mdx
index 67414f4..bd43af4 100644
--- a/iroh-services/quickstart.mdx
+++ b/iroh-services/quickstart.mdx
@@ -5,7 +5,7 @@ description: "Get started with Iroh Services in minutes"
import IrohServicesSetup from '/snippets/iroh-services-setup.mdx';
-## What is direct data rate?
+### What is direct data rate?
When two iroh endpoints connect, traffic either flows **directly** between them
(peer-to-peer) or gets routed through a relay server. Direct connections are
@@ -16,45 +16,13 @@ directly. A **high rate** means NAT traversal is working and your users are gett
the best possible connection. A **low rate** means too much traffic is falling back
to relays, which is worth investigating.
-This guide walks you through hooking up your first endpoint to Iroh Services so you can see direct data rate and other connectivity metrics for your own network. You'll need an [Iroh Services account](https://services.iroh.computer?utm_source=docs&utm_content=quickstart) with an [API key](/iroh-services/access).
-
-## Quickstart
-
-### 1. Install Rust
-
-```bash
-curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-```
-
-### 2. Get your API key
-
-Create one from your project's **Settings → API Keys** tab. See [API Keys](/iroh-services/access) for the full walkthrough. Then export it as an environment variable:
-
-```bash
-export IROH_SERVICES_API_SECRET=
-```
-
-### 3. Run the example
-
-Clone the repository and run the `quickstart` example:
-
-```bash
-git clone https://github.com/n0-computer/iroh-services
-cd iroh-services
-cargo run --example quickstart
-```
-
-Open your project's **Endpoints** page in the dashboard. Your endpoint should appear online within a few seconds. Navigate to the **Metrics** tab to see real-time direct data rate and other connectivity metrics.
-
-
-
-The **Direct Data Rate** chart shows what percentage of your traffic flowed directly between endpoints versus through a relay over time.
-

-## Build it into your own app
-
-To wire Iroh Services into your own iroh application instead of running the example, follow the full setup below.
+This guide walks you through hooking up your first endpoint to Iroh Services so
+you can see direct data rate and other connectivity metrics for your own
+network. You'll need an [Iroh Services
+account](https://services.iroh.computer?utm_source=docs&utm_content=quickstart)
+with an [API key](/iroh-services/access).
diff --git a/js-api.mdx b/js-api.mdx
new file mode 100644
index 0000000..566d13d
--- /dev/null
+++ b/js-api.mdx
@@ -0,0 +1,4 @@
+---
+title: "JavaScript API docs"
+url: "https://n0-computer.github.io/iroh-ffi/js/"
+---
diff --git a/kotlin-api.mdx b/kotlin-api.mdx
new file mode 100644
index 0000000..b0c6b3b
--- /dev/null
+++ b/kotlin-api.mdx
@@ -0,0 +1,4 @@
+---
+title: "Kotlin API docs"
+url: "https://n0-computer.github.io/iroh-ffi/kotlin/"
+---
diff --git a/languages/index.mdx b/languages/index.mdx
new file mode 100644
index 0000000..23d4262
--- /dev/null
+++ b/languages/index.mdx
@@ -0,0 +1,60 @@
+---
+title: "Overview"
+description: "Use iroh from Rust, Python, Swift, Kotlin, or JavaScript."
+---
+
+iroh is written in Rust, but the project ships first-party bindings to several other languages through [iroh-ffi](https://github.com/n0-computer/iroh-ffi). Pick the language that fits your app and follow its guide to get a working endpoint in minutes.
+
+| Language | Status | Platform notes |
+| --- | --- | --- |
+| [Rust](/languages/rust) | Official | Primary implementation |
+| [Python](/languages/python) | Official | Prebuilt wheels |
+| [Swift](/languages/swift) | Official | iOS + macOS |
+| [Kotlin](/languages/kotlin) | Official | JVM + Android |
+| [JavaScript](/languages/javascript) | Official | Node.js via N-API |
+
+All five official bindings share the same basic API surface for sending streams or datagrams over QUIC.
+
+However, the Rust language bindings are the most complete and up-to-date, and
+contain custom configuration and protocol features. If you need access to
+built-in protocol features or want to contribute to the project, Rust is the best
+place to start.
+
+## Platform support
+
+Prebuilt binaries shipped with each binding:
+
+| Platform | Rust | Python | Swift | Kotlin | JavaScript |
+| --- | --- | --- | --- | --- | --- |
+| macOS arm64 | ✓ | ✓ | ✓ | ✓ | ✓ |
+| macOS x86_64 | ✓ | ✓ | ✗ | ✓ | ✗ |
+| Linux x86_64 (glibc) | ✓ | ✓ | — | ✓ | ✓ |
+| Linux x86_64 (musl) | ✓ | ✗ | — | ✗ | ✓ |
+| Linux aarch64 (glibc) | ✓ | ✓ | — | ✓ | ✓ |
+| Linux aarch64 (musl) | ✓ | ✗ | — | ✗ | ✓ |
+| Linux armv7 | ✓ | ✗ | — | ✗ | ✓ |
+| Windows x86_64 | ✓ | ✓ | — | ✓ | ✓ |
+| Windows aarch64 | ✓ | ✗ | — | ✗ | ✓ |
+| iOS (device + sim) | ✓ | — | ✓ | — | — |
+| Android (aarch64, armv7) | ✓ | — | — | ✓ | ✓ |
+
+macOS x86_64 (Intel Mac) is a notable gap — only Rust and Python ship binaries for it. The Swift xcframework and the JavaScript N-API package are both arm64-only on macOS.
+
+
+ Get in touch and tell us what you're building. We can prioritize new bindings or help you maintain your own.
+
+
+## Build your own wrapper
+
+If you're comfortable with a little bit of Rust, you can write your own wrapper around iroh, a small application-specific binary that exposes functionality over a local HTTP server or daemon, or a full FFI wrapper from Rust to your target language. Either way, this gives you:
+
+- Full control over the API surface you expose
+- The ability to tailor it to your specific use case
+- Type-safe bindings for your language (with FFI)
+- Calls from any language (with an HTTP wrapper)
+
+While it's easy to get a first version working, **ongoing maintenance and testing is the hard part**. iroh is under active development, and keeping wrappers up to date with new releases, testing across platforms, and handling edge cases takes sustained effort. The [number0](https://n0.computer) team runs a testing lab for this purpose. If you need production-grade bindings, [contact us](https://cal.com/team/number-0/iroh-services).
+
+## WebAssembly and browsers
+
+iroh compiles to WebAssembly for use in browsers. See [WebAssembly browser support](/deployment/wasm-browser-support) for the constraints and a working browser-echo example.
diff --git a/languages/javascript.mdx b/languages/javascript.mdx
new file mode 100644
index 0000000..9b2d73b
--- /dev/null
+++ b/languages/javascript.mdx
@@ -0,0 +1,91 @@
+---
+title: "JavaScript"
+description: "Use iroh from Node.js via the iroh-ffi bindings."
+---
+
+The JavaScript bindings to iroh are published on npm as [`@number0/iroh`](https://www.npmjs.com/package/@number0/iroh) and shipped from [iroh-ffi](https://github.com/n0-computer/iroh-ffi). They're built with [napi-rs](https://napi.rs/), so the package distributes prebuilt native binaries — no Rust toolchain or local compilation needed.
+
+## Install
+
+```bash
+npm install @number0/iroh
+```
+
+Requires Node.js 20.3.0 or newer. Prebuilt N-API binaries are published for:
+
+- macOS: `arm64` (no Intel build)
+- Linux: `x86_64` and `aarch64` (glibc + musl), `armv7` (gnueabihf + musleabihf)
+- Windows: `x86_64` and `aarch64`
+- Android: `aarch64` and `armv7`
+
+## Hello, iroh
+
+```javascript
+import { Endpoint } from '@number0/iroh'
+
+const ALPN = Array.from(Buffer.from('hello-iroh/0'))
+
+const ep = await Endpoint.bind({ alpns: [ALPN] })
+console.log('endpoint id:', ep.id().toString())
+await ep.close()
+```
+
+`Endpoint.bind` applies the `n0` preset (public discovery + default relays) by default. Pass an `EndpointOptions` object to override the preset, supply a fixed secret key, or set ALPNs.
+
+## A two-peer echo
+
+A minimal sender/receiver pair over a bidirectional stream:
+
+```javascript
+// main.mjs — run: `node main.mjs receiver` or `node main.mjs sender `
+import { Endpoint, EndpointTicket } from '@number0/iroh'
+
+const ALPN = Array.from(Buffer.from('hello-iroh/0'))
+
+async function receiver() {
+ const ep = await Endpoint.bind({ alpns: [ALPN] })
+ console.log('ticket:', EndpointTicket.fromAddr(ep.addr()).toString())
+
+ const incoming = await ep.acceptNext()
+ const conn = await (await incoming.accept()).connect()
+ const bi = await conn.acceptBi()
+ const msg = await bi.recv.readToEnd(1024)
+ await bi.send.writeAll(msg)
+ await bi.send.finish()
+ await ep.close()
+}
+
+async function sender(ticketStr) {
+ const ep = await Endpoint.bind()
+ const addr = EndpointTicket.fromString(ticketStr).endpointAddr()
+ const conn = await ep.connect(addr, ALPN)
+
+ const bi = await conn.openBi()
+ await bi.send.writeAll(Array.from(Buffer.from('hello')))
+ await bi.send.finish()
+ const echoed = await bi.recv.readToEnd(1024)
+ console.log('echoed:', Buffer.from(echoed).toString('utf8'))
+ await ep.close()
+}
+
+const [cmd, arg] = process.argv.slice(2)
+if (cmd === 'receiver') await receiver()
+else if (cmd === 'sender') await sender(arg)
+else console.error('usage: receiver | sender ')
+```
+
+Start the receiver in one terminal, copy the printed ticket, and run the sender with it in another.
+
+## Next steps
+
+
+ Walkthrough of endpoints, tickets, and ALPNs with code samples in every language.
+
+
+
+ Generated TypeDoc reference for `@number0/iroh`.
+
+
+
+ Source, examples, and issue tracker for the JavaScript (and other-language) bindings.
+
diff --git a/languages/kotlin.mdx b/languages/kotlin.mdx
new file mode 100644
index 0000000..6808b09
--- /dev/null
+++ b/languages/kotlin.mdx
@@ -0,0 +1,88 @@
+---
+title: "Kotlin"
+description: "Use iroh from Kotlin on the JVM or Android."
+---
+
+The Kotlin bindings live in [iroh-ffi](https://github.com/n0-computer/iroh-ffi), generated by [uniffi-rs](https://github.com/mozilla/uniffi-rs) from the same Rust core as the Swift and Python bindings. Published to Maven Central as `computer.iroh:iroh`.
+
+## Prerequisites
+
+- A JDK (21 or newer) and `java` on your `PATH`
+- [Gradle](https://gradle.org/)
+
+## Install
+
+In your `build.gradle.kts`:
+
+```kotlin
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation("computer.iroh:iroh:1.0.0-rc.0")
+}
+```
+
+
+The published artifact is single-platform — it does not yet ship a multiplatform build, so it won't cover Android or arbitrary host triples out of the box. If you need a target the artifact doesn't cover, [build the bindings from source](#building-from-source).
+
+
+## Hello, iroh
+
+```kotlin
+import computer.iroh.*
+import kotlinx.coroutines.runBlocking
+
+private val ALPN = "hello-iroh/0".toByteArray()
+
+fun main() = runBlocking {
+ val ep = Endpoint.bind(
+ EndpointOptions(preset = presetN0(), alpns = listOf(ALPN)),
+ )
+ println("endpoint id: ${ep.id()}")
+ ep.shutdown()
+}
+```
+
+Drop this in any module that depends on the generated `computer.iroh` package. The API mirrors the Swift and Python bindings — same `Endpoint.bind`, same `EndpointOptions`, same presets.
+
+## Next steps
+
+
+ Walkthrough of endpoints, tickets, and ALPNs. Code samples are Rust but APIs map 1:1 to Kotlin.
+
+
+
+ Generated Dokka reference for the `computer.iroh` package.
+
+
+
+ Source, Kotlin tests, and issue tracker.
+
+
+## Foreground and backgrounding
+
+On Android, the OS aggressively suspends background processes — sockets get torn down, threads stop scheduling, and an iroh endpoint that was happily accepting connections a moment ago goes silent. Treat each foreground/background transition as a lifecycle event for your endpoint:
+
+- **Going to background**: call `ep.shutdown()` to close cleanly and release sockets. Persist the endpoint's secret key (`ep.secretKey().toBytes()`) somewhere durable (e.g. `EncryptedSharedPreferences`) so the next bind keeps the same endpoint id.
+- **Returning to foreground**: re-bind with `EndpointOptions(secretKey = persistedBytes, preset = presetN0(), ...)`. Discovery republishes the new direct addresses automatically.
+- **Staying live while backgrounded**: run iroh inside an [Android foreground service](https://developer.android.com/develop/background-work/services/foreground-services) (with the appropriate `FOREGROUND_SERVICE_*` permission for your use case). Without one, Android will eventually kill the network sockets regardless of how the coroutine scope is structured.
+
+For desktop JVM apps these constraints don't apply — keep the endpoint bound for the lifetime of the process and call `shutdown()` on exit.
+
+## Building from source
+
+For Android, an unsupported host, or to hack on the bindings themselves, clone iroh-ffi and generate the sources locally:
+
+```bash
+git clone https://github.com/n0-computer/iroh-ffi
+cd iroh-ffi
+cargo install cargo-make
+cargo make bindgen-kotlin # generates Kotlin sources + the cdylib
+cargo make test-kotlin # bindgen + ./gradlew test (optional)
+```
+
+The Kotlin sources land in `kotlin/lib/src/main/kotlin/` and the native library is staged into `kotlin/lib/src/main/resources/`. Point your project at that lib module via `settings.gradle.kts` to consume it instead of the Maven artifact.
+
+For Android, install the [Android NDK](https://developer.android.com/ndk) via Android Studio and point Cargo at it via `.cargo/config.toml`. The relevant ABI targets are `aarch64-linux-android`, `armv7-linux-androideabi`, `i686-linux-android`, and `x86_64-linux-android`. The full NDK config template is in the [iroh-ffi Kotlin README](https://github.com/n0-computer/iroh-ffi/blob/main/README.kotlin.md#android-development).
diff --git a/languages/python.mdx b/languages/python.mdx
new file mode 100644
index 0000000..9674068
--- /dev/null
+++ b/languages/python.mdx
@@ -0,0 +1,65 @@
+---
+title: "Python"
+description: "Use iroh from Python via the iroh-ffi bindings."
+---
+
+The Python bindings to iroh are published as prebuilt wheels on [PyPI](https://pypi.org/project/iroh/) and shipped from [iroh-ffi](https://github.com/n0-computer/iroh-ffi). The bindings are generated by [uniffi-rs](https://github.com/mozilla/uniffi-rs), so every class, method, and enum carries a docstring you can introspect from a REPL.
+
+## Install
+
+```bash
+pip install iroh
+```
+
+Prebuilt wheels are published for:
+
+- Linux: `x86_64` and `aarch64` (manylinux 2_28)
+- macOS: `arm64` and `x86_64`
+- Windows: `amd64`
+
+## Hello, iroh
+
+```python
+import asyncio
+import iroh
+
+ALPN = b"hello-iroh/0"
+
+async def main():
+ ep = await iroh.Endpoint.bind(
+ iroh.EndpointOptions(preset=iroh.preset_n0(), alpns=[ALPN])
+ )
+ print("endpoint id:", ep.id())
+
+asyncio.run(main())
+```
+
+This binds an endpoint with the `n0` preset, advertises a single ALPN, and prints the endpoint id once binding completes.
+
+## A two-peer echo
+
+The [main.py example](https://github.com/n0-computer/iroh-ffi/blob/main/python/main.py) in the iroh-ffi repo runs a sender/receiver pair over QUIC:
+
+```bash
+# Terminal 1
+python main.py serve
+
+# Terminal 2 — paste the ticket printed from Terminal 1
+python main.py connect
+```
+
+The client opens a bi-directional stream, sends `hello`, and prints what the other peer echoes back.
+
+## Next steps
+
+
+ Conceptual walkthrough of endpoints, tickets, and ALPNs. The code samples are Rust, but every API maps 1:1 to Python.
+
+
+
+ Full class, method, and enum reference generated from the uniffi-rs bindings.
+
+
+
+ Source, examples, and issue tracker for the Python (and other-language) bindings.
+
diff --git a/languages/rust.mdx b/languages/rust.mdx
new file mode 100644
index 0000000..f8f0b41
--- /dev/null
+++ b/languages/rust.mdx
@@ -0,0 +1,52 @@
+---
+title: "Rust"
+description: "Add iroh to your Rust project."
+---
+
+iroh is written in Rust, so the Rust crate is the most fully-featured way to use it. Everything in these docs assumes Rust unless explicitly tagged otherwise.
+
+## Install
+
+Add iroh to your project:
+
+```bash
+cargo add iroh
+```
+
+For tickets, common protocols, and async runtime support you'll usually want a few more:
+
+```bash
+cargo add iroh-tickets iroh-ping anyhow tracing-subscriber
+cargo add tokio --features full
+```
+
+## Hello, iroh
+
+```rust
+use anyhow::Result;
+use iroh::{Endpoint, endpoint::presets};
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ let endpoint = Endpoint::bind(presets::N0).await?;
+ endpoint.online().await;
+ println!("endpoint id: {}", endpoint.id());
+ Ok(())
+}
+```
+
+This binds an endpoint using the `N0` preset (public discovery + default relays), waits until it has a home relay, and prints its 64-character endpoint id. Run it with `cargo run`.
+
+## Next steps
+
+
+ Run two endpoints and watch them ping each other in five minutes.
+
+
+
+ Build the sender/receiver flow from scratch with tickets and a custom protocol handler.
+
+
+
+ Full type and function reference on docs.rs.
+
diff --git a/languages/swift.mdx b/languages/swift.mdx
new file mode 100644
index 0000000..3922307
--- /dev/null
+++ b/languages/swift.mdx
@@ -0,0 +1,171 @@
+---
+title: "Swift"
+description: "Build an iOS + macOS app on top of iroh via the Swift bindings."
+---
+
+The Swift bindings to iroh ship from [iroh-ffi](https://github.com/n0-computer/iroh-ffi) and are generated by [uniffi-rs](https://github.com/mozilla/uniffi-rs). The package distributes a prebuilt xcframework for iOS device, iOS simulator, and macOS, so adding iroh to your app is the same as adding any other Swift Package — no Rust toolchain required.
+
+This tutorial walks through creating a new Xcode project from scratch and wiring up a SwiftUI app that binds an iroh `Endpoint` and prints its endpoint id.
+
+## Prerequisites
+
+- macOS with [Xcode](https://developer.apple.com/xcode/) 16 or newer
+- A free Apple Developer account if you want to run on a physical device
+
+## 1. Create a new Xcode project
+
+Open Xcode → **File → New → Project…** Choose the **Multiplatform → App** template if you want one target that runs on iOS and macOS, or the iOS-only **App** template if you only care about iPhone.
+
+Fill in the product details. Use **SwiftUI** for the interface and **Swift** for the language.
+
+## 2. Add iroh as a Swift Package
+
+In Xcode, select your project in the navigator and go to **File → Add Package Dependencies…** Paste the iroh-ffi GitHub URL into the search bar:
+
+```
+https://github.com/n0-computer/iroh-ffi
+```
+
+Pick the latest released version and click **Add Package**.
+
+When Xcode prompts you to pick a product, check **IrohLib** and add it to your app target.
+
+
+
+
+
+Xcode resolves the package, downloads the prebuilt xcframework, and links it into your target. You should see **IrohLib** appear under **Package Dependencies** in the navigator.
+
+## 3. Link Network.framework on iOS
+
+iroh's Rust core uses [Network.framework](https://developer.apple.com/documentation/network) for interface enumeration on iOS. Add a linker flag scoped to iOS:
+
+Select the app target → **Build Settings** → search **Other Linker Flags** → add **`-framework Network`** to the **iphoneos** and **iphonesimulator** SDK rows.
+
+
+
+
+
+If you skip this step the iOS build fails with `Undefined symbols: _nw_interface_get_index`.
+
+## 4. Grant network entitlements (macOS)
+
+If you target macOS, the App Sandbox blocks both inbound and outbound networking by default. Open the target's **Signing & Capabilities** tab. Under **App Sandbox** check **Incoming Connections (Server)** and **Outgoing Connections (Client)**.
+
+
+
+
+
+Your `.entitlements` file should now contain:
+
+```xml
+com.apple.security.network.client
+
+com.apple.security.network.server
+
+```
+
+## 5. Disable previews (Xcode 16 workaround)
+
+Xcode 16's preview pipeline tries to link `SwiftUICore.framework` directly when a Swift Package is in the graph, which fails with *"product being built is not an allowed client of it"*. Until Apple ships a fix, turn previews off for the app target:
+
+Build Settings → search **Enable Previews** → set both **Debug** and **Release** to **No**.
+
+You'll lose Xcode's preview canvas, but the app builds and runs normally.
+
+## 6. Hello, iroh
+
+Replace `ContentView.swift` with a small SwiftUI view that binds an endpoint on appear and renders its id:
+
+```swift
+import SwiftUI
+import IrohLib
+
+struct ContentView: View {
+ @State private var endpointId: String = "binding…"
+ @State private var endpoint: Endpoint?
+
+ var body: some View {
+ VStack(spacing: 12) {
+ Text("My endpoint id")
+ .font(.headline)
+ Text(endpointId)
+ .font(.system(.caption, design: .monospaced))
+ .textSelection(.enabled)
+ .multilineTextAlignment(.center)
+ .padding(.horizontal)
+ }
+ .padding()
+ .task { await bind() }
+ }
+
+ private func bind() async {
+ do {
+ let ep = try await Endpoint.bind(options: EndpointOptions(
+ preset: presetN0(),
+ alpns: [Data("hello-iroh/0".utf8)]
+ ))
+ endpoint = ep
+ endpointId = ep.id().description
+ } catch {
+ endpointId = "bind failed: \(error)"
+ }
+ }
+}
+```
+
+This binds an endpoint using the `n0` preset (public discovery + default relays), advertises a single ALPN, and surfaces the 64-character hex id once binding completes.
+
+## 7. Build and run
+
+Pick a destination and hit Run.
+
+- **macOS**: choose **My Mac**. The app window opens and shows the endpoint id within a beat.
+- **iOS Simulator**: choose any iPhone simulator destination.
+- **iOS device**: select your device. You'll need to trust your developer certificate in **Settings → General → VPN & Device Management** the first time.
+
+Two different installs print two different endpoint ids — that's expected, since each launch generates a fresh `SecretKey`. If you want a stable identity across launches, persist `endpoint.secretKey().toBytes()` (e.g. in `UserDefaults` for a demo, Keychain for production) and pass it back via `EndpointOptions(secretKey: ...)` on subsequent launches.
+
+## Building against an unreleased iroh-ffi
+
+If you need an unreleased fix or want to hack on the bindings themselves, you can build the xcframework locally and consume the package from a local checkout.
+
+```bash
+git clone https://github.com/n0-computer/iroh-ffi
+cd iroh-ffi
+rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios aarch64-apple-darwin
+cargo install cargo-make
+cargo make swift-xcframework
+```
+
+Then in Xcode, **File → Add Package Dependencies… → Add Local…** and pick the `iroh-ffi` directory. The rest of the steps above still apply.
+
+## Next steps
+
+
+ Build a peer-to-peer ping over iroh and learn how tickets, ALPNs, and routers fit together.
+
+
+
+ Full DocC reference for `IrohLib` — every type, method, and option generated from the bindings.
+
+
+
+ Learn what an endpoint actually is and how its identity, addresses, and discovery interact.
+
+
+
+ Understand the copy-pasteable strings that let two endpoints find each other.
+
+
+## Troubleshooting
+
+**`Undefined symbols: _nw_interface_get_index` on iOS.** You skipped step 3 — add `-framework Network` to **Other Linker Flags** for the iOS SDKs.
+
+**`product being built is not an allowed client of SwiftUICore`.** You skipped step 5 — set **Enable Previews** to **No**.
+
+**`No such module 'IrohLib'`.** The package didn't resolve. Pull down the package from **File → Packages → Reset Package Caches** and try the resolve again.
+
+**`bind failed: NoNetwork` or hang on macOS.** Check **Signing & Capabilities** — both **Incoming** and **Outgoing** network sandbox boxes need to be checked.
+
+**`The developer disk image could not be mounted on this device`.** Your device is running a newer iOS than your Xcode supports. Update Xcode (App Store for stable, [developer.apple.com](https://developer.apple.com/download/applications) for betas) so the matching Developer Disk Image is available, or fall back to the simulator while you wait.
diff --git a/python-api.mdx b/python-api.mdx
new file mode 100644
index 0000000..5681aff
--- /dev/null
+++ b/python-api.mdx
@@ -0,0 +1,4 @@
+---
+title: "Python API docs"
+url: "https://n0-computer.github.io/iroh-ffi/python/"
+---
diff --git a/snippets/iroh-services-setup.mdx b/snippets/iroh-services-setup.mdx
index 87988ed..bf07d0d 100644
--- a/snippets/iroh-services-setup.mdx
+++ b/snippets/iroh-services-setup.mdx
@@ -1,16 +1,21 @@
### Add the Iroh Services Client
-Add the `iroh-services` crate to your `Cargo.toml`:
+In Rust, add the `iroh-services` crate to your `Cargo.toml`:
```
cargo add iroh-services
```
+The Python, Swift, and Kotlin bindings ship the services client as part of
+`iroh-ffi`, with no extra dependency needed.
+
### Connect Your Endpoint
-Then, in your code, create a client and connect your endpoint to Iroh Services.
+Then, in your code, create a client and connect your endpoint to Iroh Services. Giving the endpoint a name makes it easy to identify on the dashboard; unnamed endpoints are harder to distinguish from each other.
+
+
-```rust
+```rust Rust
use iroh::{Endpoint, endpoint::presets};
use iroh_services::Client;
@@ -22,9 +27,7 @@ async fn main() -> anyhow::Result<()> {
// Wait for the endpoint to be online
endpoint.online().await;
- // Create the Iroh Services client with your API key. Giving the endpoint
- // a name makes it easy to identify on the dashboard. Unnamed endpoints
- // are harder to distinguish from each other.
+ // Create the Iroh Services client with your API key.
let client = Client::builder(&endpoint)
.api_secret_from_str("YOUR_API_KEY")?
.name("my-endpoint")?
@@ -37,6 +40,57 @@ async fn main() -> anyhow::Result<()> {
}
```
+```python Python
+import asyncio
+import iroh
+
+async def main():
+ ep = await iroh.Endpoint.bind(iroh.EndpointOptions(preset=iroh.preset_n0()))
+ await ep.online()
+
+ client = await iroh.ServicesClient.create(
+ ep,
+ iroh.ServicesOptions(api_secret="YOUR_API_KEY", name="my-endpoint"),
+ )
+
+ # Your endpoint is now reporting metrics to Iroh Services!
+
+asyncio.run(main())
+```
+
+```swift Swift
+import IrohLib
+
+let endpoint = try await Endpoint.bind(options: EndpointOptions(preset: presetN0()))
+await endpoint.online()
+
+let client = try await ServicesClient.create(
+ endpoint: endpoint,
+ options: ServicesOptions(apiSecret: "YOUR_API_KEY", name: "my-endpoint")
+)
+
+// Your endpoint is now reporting metrics to Iroh Services!
+```
+
+```kotlin Kotlin
+import computer.iroh.*
+import kotlinx.coroutines.runBlocking
+
+fun main() = runBlocking {
+ val endpoint = Endpoint.bind(EndpointOptions(preset = presetN0()))
+ endpoint.online()
+
+ val client = ServicesClient.create(
+ endpoint,
+ ServicesOptions(apiSecret = "YOUR_API_KEY", name = "my-endpoint"),
+ )
+
+ // Your endpoint is now reporting metrics to Iroh Services!
+}
+```
+
+
+
### View Your Endpoint on the Dashboard
Go to your project's **Endpoints** page. You should see your endpoint listed as
diff --git a/snippets/relay-endpoint-config.mdx b/snippets/relay-endpoint-config.mdx
index 1cccd22..d76641e 100644
--- a/snippets/relay-endpoint-config.mdx
+++ b/snippets/relay-endpoint-config.mdx
@@ -1,4 +1,6 @@
-```rust
+
+
+```rust Rust
use iroh::Endpoint;
use iroh::relay::RelayUrl;
@@ -15,3 +17,50 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}
```
+
+```python Python
+import asyncio
+import iroh
+
+async def main():
+ relay_mode = iroh.RelayMode.custom_from_urls([
+ "YOUR_RELAY_URL_US",
+ "YOUR_RELAY_URL_EU",
+ ])
+ ep = await iroh.Endpoint.bind(
+ iroh.EndpointOptions(preset=iroh.preset_n0(), relay_mode=relay_mode)
+ )
+
+asyncio.run(main())
+```
+
+```swift Swift
+import IrohLib
+
+let relayMode = try RelayMode.customFromUrls(urls: [
+ "YOUR_RELAY_URL_US",
+ "YOUR_RELAY_URL_EU",
+])
+let ep = try await Endpoint.bind(options: EndpointOptions(
+ preset: presetN0(),
+ relayMode: relayMode
+))
+```
+
+```kotlin Kotlin
+import computer.iroh.*
+import kotlinx.coroutines.runBlocking
+
+fun main() = runBlocking {
+ val relayMode = RelayMode.customFromUrls(listOf(
+ "YOUR_RELAY_URL_US",
+ "YOUR_RELAY_URL_EU",
+ ))
+ val ep = Endpoint.bind(
+ EndpointOptions(preset = presetN0(), relayMode = relayMode),
+ )
+ ep.shutdown()
+}
+```
+
+
diff --git a/swift-api.mdx b/swift-api.mdx
new file mode 100644
index 0000000..00748a8
--- /dev/null
+++ b/swift-api.mdx
@@ -0,0 +1,4 @@
+---
+title: "Swift API docs"
+url: "https://n0-computer.github.io/iroh-ffi/swift/documentation/irohlib/"
+---