RiftRift

Mobile SDK

iOS SDK

Native Swift SDK for deep linking, attribution, user binding, and conversion tracking. Built with Rust and compiled to a Swift Package via UniFFI. Install ID persists across app reinstalls via the iOS Keychain.

Quick Start

1

Add the Swift Package

In Xcode, File → Add Package Dependencies and enter the repository URL:

https://github.com/saltyskip/rift

Choose Up to Next Major Version from 0.2.3 and add the RiftSDK library to your app target. Requires iOS 15+.

Note: Prefer a vendored copy? Download rift-ios-sdk-*.tar.gz from GitHub Releases, extract it, and use Add Local on the ios/ directory instead.
2

Initialize (one line)

You need a publishable key. The convenience constructor auto-wires Keychain storage and reads the app version from Bundle.main.

import RiftSDK

// One line — Keychain storage, app version, all defaults.
let rift = RiftSdk.create(publishableKey: "pk_live_YOUR_KEY")
Note: The SDK generates a persistent install_id (UUID) on first launch and stores it in the Keychain. It survives app deletion and reinstallation.
3

Bind the user (one line)

Call setUserId wherever you handle your user session — after signup, login, or session restore. Safe to call on every launch. The SDK handles persistence, sync, and retry.

// Wherever you know the user is signed in:
Task {
    try? await rift.setUserId(userId: currentUser.id)
}
4

Track conversions (one line)

Fire a conversion event whenever a user does something worth counting. The SDK reads the bound user_id and POSTs to the Rift API using your publishable key.

// On trade completion, purchase, signup — whatever you're measuring:
try await rift.trackConversion(
    conversionType: "trade",
    idempotencyKey: orderId,
    metadata: ["asset": "ETH", "side": "buy"]
)

The server dedupes via idempotencyKey, so retries are safe.

Deferred Deep Linking

5

One-call deferred deep link (3 lines)

On first launch, check the pasteboard for a Rift link. The SDK detects whether a URL is present, parses it, reports attribution, and returns the link data for navigation — all in one call.

// On first launch:
if let result = try await rift.checkDeferredDeepLinkFromPasteboard() {
    if let deepLink = result.iosDeepLink {
        handleDeepLink(deepLink)
    }
}
Note: It uses UIPasteboard.detectPatterns(for:) to check for a URL without reading the contents, so it only touches the pasteboard — and only triggers the iOS 16+ paste banner — when a URL is actually present. On a match it clears the pasteboard so a re-launch doesn't re-attribute. Pass your own text via checkDeferredDeepLink(clipboardText:) if you need custom gating.

Handle an incoming link

6

Resolve a link that opened the app

When the app is already installed, the OS hands you the link URL directly via a Universal Link (or your custom scheme) — no clipboard involved. Record the attribution and route to the destination:

import RiftSDK

// SwiftUI — on your App or root view:
.onOpenURL { url in
    // Your link id is the last path component, e.g.
    // https://go.yourcompany.com/summer-sale
    guard let linkId = url.pathComponents.last, !linkId.isEmpty else { return }
    Task {
        try? await rift.attributeLink(linkId: linkId)          // record attribution
        if let link = try? await rift.getLink(linkId: linkId), // resolve destination
           let deepLink = link.iosDeepLink {
            handleDeepLink(deepLink)
        }
    }
}
Note: Deferred deep linking (above) covers the not-installed case via the pasteboard. This covers the installed case, where iOS delivers the link URL straight to your app on tap.

Click Tracking

7

Record a click

If your app opens Rift links internally (e.g., share sheets), record the click:

let result = try await rift.click(linkId: "summer-sale")
print("Platform: \(result.platform)")
print("Deep link: \(result.iosDeepLink ?? "none")")

Logout

Call clearUserId() when the user signs out. The install ID is preserved — only the user binding is removed.

try rift.clearUserId()

API Reference

Constructors

ConstructorDescription
RiftSdk.create(publishableKey:)Convenience. Auto-wires Keychain storage + app version. Recommended.
RiftSdk(config:, storage:)Full control. Pass custom RiftConfig and RiftStorage implementation.

Methods

MethodReturnsDescription
setUserId(userId:)Void (async throws)Bind the install to a user. Persists + syncs + retries on next launch.
trackConversion(conversionType:, idempotencyKey:, metadata:?)Void (async throws)Fire a conversion event. POSTs to the Rift API via publishable key.
checkDeferredDeepLinkFromPasteboard(clearOnMatch:)DeferredDeepLinkResult? (async throws)Recommended. Detects a URL on the pasteboard without reading it (no paste banner), then resolves + attributes. Clears the pasteboard on a match.
checkDeferredDeepLink(clipboardText:)DeferredDeepLinkResult? (async throws)Lower-level: you supply the clipboard text. Parses, attributes, and fetches link data.
clearUserId()Void (throws)Remove stored user binding. Call on logout.
installId()String (throws)Persistent install UUID. Generates on first call.
attributeLink(linkId:)Bool (async throws)Report a deferred attribution using the SDK's internal install_id + app version. Persists and retries on next launch if the network call fails.
click(linkId:)ClickResult (async throws)Record a click and return link data.
getLink(linkId:)GetLinkResult (async throws)Fetch link data without recording a click.

Free functions

FunctionDescription
parseClipboardLink(text:, allowedHosts:)Low-level: extracts a link ID from a URL whose host is in allowedHosts. Most apps use checkDeferredDeepLinkFromPasteboard() instead.