Shuttle Docs

Proposed base SDK model and business stories

The wallet model, the durable operation contract and the language bindings.

Updated2026-09-16
RuntimesTypeScript (browser/server), React adapter (browser), Python and Go (server)

Status and source boundary

This document adopts the 2026-09-14 SDK public-surface review and a 2026-09-15 refresh of the upstream generated client as sources for this documentation revision. It defines proposed target behavior for later implementation. It is not an available SDK, an implementation-ready specification, proof of cryptographic behavior, or an amendment to the approved Streams PRD.

The refreshed upstream source revision matched its current remote head at review time. Method names and broad input/result roles in documents 19–24 are verified source facts; the own SDK shapes and safety contracts remain proposed design. Before implementation, the owning team must recheck source freshness and retain interoperable behavior evidence.

Smallest justified public surface#

The original focused wallet descriptions remain, including getSendTransactionStatus as a supporting compatibility/detail read. The grouped catalogs add the complete verified user, organization, approval, policy and transaction inventories plus application-configuration, session-profile and organization OAuth-credential boundaries. The collection contains 25 documents: 17 focused wallet/operation references and eight grouped or foundation documents. It remains a proposed design catalog, not a released SDK.

The primary calls are:

  1. createWallet
  2. getWallet
  3. getWallets
  4. updateWallet
  5. signTransaction
  6. sendTransaction (ethSendTransaction is the EVM compatibility alias)
  7. getWalletAddressBalances
  8. importWallet
  9. exportWallet
  10. createPolicy
  11. updatePolicy
  12. signMessage
  13. signTypedData
  14. watchWallet
  15. recoverWalletAccess
  16. getOperation

No separate public calls are required for custody-share transport, transport-key retrieval, status-check loops, share rotation, reconnection or replay. Approval review is the explicit business domain in Approvals; raw proof transport and vote storage remain internal mechanics behind those calls.

Wallet and ownership model#

One wallet is one network-scoped signing key/address with a stable wallet ID and an immutable-at-creation controlMode: embedded or server. Public reads expose safe summaries of control mode, owner/service authority, address, supported actions, grants, limits and effective policy assignments, never credentials, signing material or authorization proofs. The base target does not promise a mnemonic, derived accounts, derivation paths, or mnemonic export. V1 has no silent or in-place migration between modes.

controlMode: embedded End user browser / device Owner-controlled protected runtime Shuttle backend authenticates actor · evaluates grants and policy Authorization comes from the wallet owner. Backend coordination never transfers owner authority. Browser code is an untrusted caller. controlMode: server Named service treasury · payouts · fees Protected remote signer Policies · transaction limits · approval rules checked at signing time, not after the fact End-user authentication never grants service authority. No server credential is embedded in browser code. Mode is immutable at creation — V1 has no migration.
One wallet is one network-scoped key and address with a stable wallet ID and a controlMode fixed at creation. Neither mode returns raw custody material. Public reads expose safe summaries of mode, authority, address, grants, limits and effective policy assignments — never credentials, signing material or authorization proofs.

The TypeScript core may run in a browser or on a server; a React adapter exposes client-component integration, while Python and Go remain server bindings. Browser code is an untrusted caller. The backend independently authenticates the actor, evaluates wallet grants and policies, binds authorization to exact operation material, and never treats a session token as blanket signing, export, recovery, treasury or policy consent. An embedded wallet uses an owner-controlled protected runtime; backend coordination never transfers owner authority. A server wallet is deliberately service-controlled for treasury, payouts and network fees; its protected remote signer acts only as the named service identity within policies, transaction limits and approval rules. End-user authentication never grants service authority. Neither mode returns raw custody material or embeds server credentials in browser code.

The application may configure one authorization handler that reaches the authority appropriate to the mode: owner-controlled for embedded, service-policy/approver controlled for server. For one operation the SDK chooses exactly one path: complete under existing grants, invoke the handler and submit once, or return requiresAction. Automatic and manual continuation never both submit. Authentication establishes identity and session context; authorization still binds action, wallet, mode, actor/service identity, request ID, payload identity and expiry.

Every authorization response binds the exact action, wallet, actor, request ID, payload identity and expiry. It is valid for that operation only and cannot authorize a different request, changed payload or later action. Proof bytes and accumulated responses are stored separately from immutable operation material, so adding a valid response to the original operation does not create a changed-material conflict. Where agreement from multiple authorized actors is required, the operation exposes progress and reaches succeeded, denied, expired, failed or still-requiresAction; agreement is never a dead-end string with no completion path.

Exact owner/service proof mechanisms, recovery factors, signing algorithms, canonical encodings, agreement rules and custody transport remain implementation-gate decisions. Unknown mechanisms cannot weaken outcomes or silently change control mode. A backend credential is not embedded-owner proof, cannot authorize its own recovery and cannot elevate an end user into a server-wallet service role.

Unified durable operation contract#

Every wallet, policy, rights, signing, send, import/export or wallet-recovery business mutation in documents 01–17 accepts a caller-generated stable requestId. The backend atomically binds it to immutable operation kind, target, actor, payload identity and required authority context. Changing proof bytes are accumulated separately as responses bound to that immutable operation. Browser authentication is separate: OTP, OAuth, passkey, session, MFA and account-recovery calls use their documented challenge/correlation identity, expiry and one-time proof. getOperation never retrieves an OTP response, session token, session secret key or authentication secret. If a future accepted contract routes MFA-policy administration through this durable business-operation envelope, it must say so explicitly.

Caller requestId (stable) submit Atomic binding kind · target · actor payload identity authority context requiresAction succeeded denied expired failed authorization restart, timeout or lost response → getOperation(requestId) recovers the same record
One request identity, one recorded outcome. The backend binds the request ID to immutable operation material the moment it is accepted. Authorization proof accumulates alongside that record instead of changing it, so adding a valid response never creates a changed-material conflict. Repeating the same ID with the same material converges on the recorded result; repeating it with different material conflicts and creates no additional effect.

Ordinary business methods may await internally within the caller's deadline. Applications use the grouped approval calls only when review or multi-party progress is part of their workflow; they do not manage raw proof transport or a repeated send-status loop. getSendTransactionStatus remains available only when chain-specific send detail is needed.

Transfers and signed event contract#

sendTransaction and watchWallet form a shared business interface for sending assets and receiving deposit/transfer lifecycle notifications. Deposits adapt existing standalone Streams monitoring contracts. Outgoing transfer lifecycle requires a separate proposed adapter/source and is not claimed as current Streams behavior. The SDK hides address registration, connection, subscriptions, cursors, bounded reconnection and replay.

Chain activity deposit / transfer Service signs event eventId · eventKind raw amount · time correlation identity Verifier exact raw bytes, then parse window · key ID · tenant wallet · event identity Handler application code invalid signature · stale or replayed · cross-tenant binding → rejected before the handler runs Application transaction business effect + processed-eventId receipt, committed together, durably SDK checkpoint advances only after the handler reports success crash after commit, before checkpoint → harmless redelivery Delivery is at least once; exactly-once is not claimed. An unrecoverable gap returns resyncRequired — the SDK never jumps silently. Reorg and finality corrections append a new event and never erase history.
Notifications are signed by service event-authentication keys, never wallet fund-signing keys. A valid notification proves sender authenticity and integrity — not chain execution or finality. Browser delivery uses an authenticated wallet-scoped channel; no webhook secret ever enters browser code.

Each event includes stable eventId, eventKind (incomingDeposit or outgoingTransfer), direction, lifecycle status/sequence, wallet/address, asset, exact raw amount, evidence time, and available transfer/operation/request/transaction correlation. A shared correlation identity prevents one economic movement from being credited as both transfer and deposit. Delivery is at least once; exactly-once is not claimed.

Notifications are signed by service event-authentication keys, never wallet fund-signing keys. A backend verifier authenticates exact raw bytes before parsing, then checks timestamp window, key ID/version, tenant, wallet and event identity. Invalid signature, stale/replayed event or cross-tenant binding is rejected before handler invocation or checkpoint advance. Browser delivery uses an authenticated wallet-scoped channel and, if directly signed, asymmetric public verification; no webhook secret enters browser code. A valid notification proves sender authenticity/integrity, not chain execution or finality.

The handler reports success only after durably committing its business effect and processed-eventId receipt in its own transaction. Only then may the SDK advance its checkpoint. A crash after application commit but before SDK checkpoint causes harmless redelivery. External effects need application idempotency or a transactional outbox. Handler error advances no checkpoint. An unrecoverable gap returns resyncRequired; the SDK never jumps silently. Reorg/finality correction appends a new event and never erases history.

Runtime and binding availability#

Surface TypeScript browser TypeScript server React adapter Python server Go server
Wallet create/read/update, sign, send, balances, watch and operation reads Proposed target when browser-safe and scoped Proposed target Calls the TypeScript core in a client component; no extra authority Proposed target Proposed target
Import/export/recovery Proposed only through an authorized secure device/recipient flow Proposed with an explicit secure recipient boundary TypeScript-core orchestration only; no raw material in component state or logs Proposed with an explicit secure recipient boundary Proposed with an explicit secure recipient boundary
Policy and rights administration Proposed only for explicit owner/admin authority Proposed target Admin mediation through the core; no dedicated hook is implied Proposed target Proposed target
Passkey, OTP, social and external-wallet authentication Current browser/core source shapes inform the proposed target Server validates or coordinates only Built-in authentication UI supports its documented subset No browser equivalent; server coordination only No browser equivalent; server coordination only
Session creation and profiles Current core supports read-only/read-write sessions and profile create/get/list Current core methods exist; target wrapper remains proposed Built-in auth currently selects read-write; an authorized component may call the core directly, with no invented profile hook Proposed server binding; no browser stamping Proposed server binding; no browser stamping
Organization OAuth credentials Not exposed: confidential admin boundary Current core methods inform the proposed admin binding Not exposed Proposed secure server/admin binding Proposed secure server/admin binding
User and organization administration Scoped self-service reads only where explicitly granted Proposed privileged administration Client-component mediation, never authority Proposed server binding Proposed server binding
Approval, policy and transaction catalogs Proposed when scoped grants and fresh action authorization allow it Proposed target Client-component mediation through the core Proposed server binding Proposed server binding

React is a framework adapter, not a fourth programming language. Hooks run only inside client components; server-side rendering must not read browser credentials, session storage, device shares or browser-only APIs. The examples are proposed API shapes, not real package imports. The browser receives only public configuration and opaque identifiers; application secrets and privileged server credentials remain server-side.

One business contract · one set of stable JSON field names TypeScript browser + server core Promise<T> · AbortSignal requestId · bigint React adapter client components only calls the TypeScript core no extra authority Python server binding async · task cancellation request_id · UNSET Go server binding context.Context first RequestID · big.Int Shared codec exact monetary quantities as canonical unsigned base-10 JSON strings — no float, sign, exponent or leading zero
React is a framework adapter, not a fourth language. Hooks run only inside client components; server-side rendering must not read browser credentials, session storage, device shares or browser-only APIs. Browser-specific authentication has no promised Python or Go equivalent.

Complete grouped catalogs#

Proposed language bindings#

The wallet-capability bindings cover TypeScript, Python and Go. TypeScript additionally supports browser use and a React adapter. Browser-specific authentication has no promised Python or Go browser equivalent. All bindings share one business contract and proposed stable JSON field names while presenting native naming and control flow. The signatures, wire names and helper types below are illustrative target API design; they do not identify an existing server wire contract, released package, import path or executable implementation.

Naming and DTO mapping#

Shared meaning TypeScript Python Go JSON wire name
Request identity requestId request_id RequestID request_id
Wallet identity walletId wallet_id WalletID wallet_id
Expected version expectedVersion expected_version ExpectedVersion expected_version
Consumer identity consumerId consumer_id ConsumerID consumer_id
Authorization response authorization authorization Authorization authorization
Action-required request authorizationRequest authorization_request AuthorizationRequest authorization_request
Operation state string union string-valued Enum named string type and constants state

SDK-facing exact monetary quantities use bigint in TypeScript, arbitrary-precision int in Python, and big.Int values in Go. The shared codec serializes those quantities as canonical unsigned base-10 JSON strings and rejects JavaScript number, floating point, signs, fractions, exponent notation and non-canonical leading zeroes. Opaque cursors, hashes and identifiers remain strings even when they contain only digits. A Go binding must copy caller-owned big.Int input before retaining it and return independent values so later caller mutation cannot change recorded operation material.

Enums serialize to the stable documented string value, not a language ordinal. For example, the operation state value requiresAction maps to a TypeScript string union member, a Python enum member such as OperationState.REQUIRES_ACTION, and a Go constant such as OperationStateRequiresAction; every one encodes as "requiresAction". A binding must reject an enum value that the shared contract does not support rather than silently selecting a default.

Omission, null and explicit updates#

An omitted optional field means “not supplied.” JSON null is accepted only for a field that the shared DTO explicitly declares nullable; it never means clear, default or zero by convention.

Wallet and policy updates carry explicit change-set actions such as set name, clear name, grant right, revoke right, assign policy and remove policy. No binding infers a destructive change from an omitted field, null, None, nil, an empty string, a zero number or an empty collection. Bindings serialize the same ordered change-set material and must not split one atomic update into several calls.

Asynchrony, cancellation and errors#

All network-facing calls are asynchronous. TypeScript returns Promise<T> and accepts an optional AbortSignal; Python methods are async and propagate task cancellation; Go methods take context.Context first and return (T, error). The generic Go result notation is conceptual; the implementation must select and publish a supported Go language version before release. Stable transport and SDK error categories remain machine-testable language types or values rather than parsed messages. They are distinct from an operation envelope successfully returned in the business terminal states denied, expired or failed.

Cancellation before SDK dispatch, or a confirmed backend rejection before effect, has no mutation effect. Once the SDK dispatches a mutation, cancellation, timeout or transport loss cancels only the caller's wait: the outcome is unknown even when backend acceptance was not observed, because acceptance may occur later. Recover with the same requestId through getOperation. A binding must never retry with a new request identity or changed material. Read cancellation creates no effect. Watch cancellation stops only that consumer and does not acknowledge an event whose handler did not complete successfully; if the application committed but the SDK checkpoint did not advance, the event is redelivered.

Authorization continuation#

The configured handler shapes are AuthorizationRequest => Promise<AuthorizationResponse> in TypeScript, an async callable in Python, and func(context.Context, *AuthorizationRequest) (*AuthorizationResponse, error) in Go. The automatic handler path either submits the response once or returns its own failure. The manual path returns requiresAction; the caller then supplies the response in the language-specific getOperation input. A response is never submitted by both paths.

One operation SDK picks exactly one path A · Complete under existing grants no handler is invoked B · Automatic — handler, submit once the handler returns the response, the SDK submits it C · Manual — return requiresAction the caller supplies the response to getOperation Terminal outcome succeeded · denied expired · failed Automatic and manual continuation never both submit. Every authorization response binds the exact action, wallet, actor, request ID, payload identity and expiry — and is valid for that one operation only.
Exactly one path per operation. A response cannot authorize a different request, a changed payload or a later action. Where agreement from several authorized actors is required, the operation exposes progress and still reaches a terminal state — agreement is never a dead-end string with no completion path.

Illustrative equivalent flow#

These snippets illustrate equivalent business semantics without claiming real packages or runnable imports:

const operation = await sdk.sendTransaction({ requestId, walletId, network, transaction: transfer });
if (operation.state === "requiresAction") {
  const authorization = await ownerHandler(operation.authorizationRequest);
  await sdk.getOperation({ requestId, authorization });
}
const recovered = await sdk.getOperation({ requestId });
await sdk.watchWallet({ walletId, consumerId, signal }, async event => {
  await application.commitEffectAndProcessedEvent(event);
});

The React adapter is a client-component convenience layer over the same TypeScript contract. The hook is never invoked outside a component or during server rendering, and the presence of session enables UI only; it is not wallet-action authorization.

Shared continuation rule#

These examples use explicit continuation because the initial call returned requiresAction. When a configured handler is invoked automatically, the SDK submits its response internally and must not also return a manual submission path for that response. Continuation through getOperation advances only the already-recorded logical operation; it does not create a new action or change immutable material.

Five complete business stories#

Each story can start in a permitted browser/React or server runtime. Authentication establishes identity only. Embedded-wallet actions use owner grants/approval; server-wallet actions use the named service identity, policy, limits and approvers. Server credentials never appear in browser configuration.

1. Create a wallet#

Integrator: explicitly chooses embedded or server, network, and the mode-appropriate owner/service identity, policies and limits; persists a stable request ID; calls createWallet; and stores the result only after success.

SDK: reject missing/ambiguous mode, bind the operation, obtain mode-appropriate authorization, perform internal custody setup, and return one network-scoped wallet/address without exposing transport mechanics.

Authority: login is not creation consent or treasury authority. Embedded creation uses the owner boundary; server creation uses service-policy/approver authority. Denial or expiry creates no wallet and no mode conversion.

2. Receive money#

Integrator: a browser may display an authorized stream, while durable processing belongs in an application store that atomically commits each deposit/transfer effect plus its processed-event receipt. A backend consumer calls watchWallet with a durable identity and deduplicates by stable event/correlation identity.

SDK: expose only a wallet-scoped read capability, validate signed envelopes before the handler, advance its separate checkpoint only after handler success, resume from that checkpoint, and deliver deposit plus separately sourced outgoing-transfer lifecycle updates and appended corrections. Signature proves sender/integrity, not finality.

Owner participation: an authenticated actor still needs read scope, but observation requires no signing consent. A processing failure advances no checkpoint. An unrecoverable gap returns resyncRequired; no false success or silent gap is allowed.

3. Send money#

Integrator: a browser/React client or backend constructs an exact network-scoped native/token transfer request, persists request ID, calls sendTransaction, and treats submission separately from execution/finality. Legacy EVM callers may use the ethSendTransaction alias. Recover through getOperation after timeout or restart.

SDK: verify wallet/mode/network, build family-specific mechanics, constrain fees, evaluate grants/policies/limits, obtain embedded-owner or server-service approval, create at most one logical effect, retain identity, and expose meaningful state/events. Solana may use a separately authorized native fee payer; EVM gas funding is a separate treasury transfer, never same-transaction sponsorship.

Authority: login alone cannot send. Embedded handlers approve exact wallet/network/asset/recipient/amount/fee/request. Server-wallet payouts require service identity, policy, limit and configured approvers. Changed material requires fresh authorization; uncertainty never triggers fresh signing.

4. Revoke access#

Integrator: an authenticated browser/admin UI or backend calls updateWallet with stable request ID, expected version and explicit right revocation. If policy applicability must also change, it names that separate change explicitly. It verifies the resulting wallet authority with getWallet.

SDK: atomically apply the authorized change or none, invalidate the revoked actor according to the backend contract, and return the next wallet authority/version. It does not confuse removing a restrictive policy with revoking an actor's right.

Revocation boundary: the wallet-version commit and denial of new authorization by the revoked actor are one atomic outcome. An operation not yet at an irreversible signing, export-disclosure or network-submission boundary must pass an execution authorization check; after revocation it is denied even if previously staged or durably approved. A signature, export disclosure or submitted transaction already produced before the boundary cannot be clawed back; it remains visible in the in-flight operation result and must be reconciled. No ordinary grant transfers owner identity, and this base surface has no ownership-transfer operation.

Owner participation: a session or UI role cannot elevate itself. Explicit owner/admin authority is required whenever the existing bounded grant cannot revoke the target right. Denial, stale version, insufficient agreement or unknown outcome leaves success unclaimed and is recoverable by request ID.

5. Recover access#

Integrator: after separately recovering or establishing account identity when necessary, a secure device/browser flow or backend coordinator calls recoverWalletAccess with stable request ID, explicit recovery basis, new owner-controlled recipient and trusted wallet-owner proof independent of the lost/compromised factor. It verifies an allowed action after recovery.

SDK: keep account/session recovery separate from wallet custody, validate the wallet recovery basis and required MFA/agreement, rotate only the authorization/device/share binding, preserve the wallet signing key, wallet ID, network and address, establish the new owner-controlled binding and retire the superseded binding under the accepted contract.

Owner participation: the one handler collects the required independent proof/agreement. A recovered login session, ordinary linked email, the lost factor alone and the application backend's own credential cannot authorize wallet recovery. Import is not recovery. A protected device runtime may perform local reconstruction/re-encryption, but the public result exposes no raw share and nothing silently transfers custody to the integrator server.

Requirements traceability#

ID Adopted requirement Owning sections/files Documentation acceptance check
SDK-R01 Expose business actions/results; hide service mechanics Smallest surface; operation/event sections; all method files No low-level share, transport, reconnect, rotation or raw proof call is primary
SDK-R02 Support message and structured-data signing 13-sign-message.md; 14-sign-typed-data.md Exact payload/signer binding and positive/negative signature checks exist
SDK-R03 Expose owner and grant/revoke rights Wallet model; 02-get-wallet.md; 04-update-wallet.md Sign/export/restriction rights are readable and explicitly mutable
SDK-R04 Read and change effective policy applicability 02-get-wallet.md; 04-update-wallet.md; policy files Assignment/removal is distinct from rule definition and right revocation
SDK-R05 Recover mutation results without repeated effects Unified operation contract; 17-get-operation.md; mutation files Request binding, conflict, uncertainty, finite retention and restart recovery are explicit
SDK-R06 Deliver wallet events with stable processing boundary Wallet event contract; 15-watch-wallet.md; receive story At-least-once, application dedup receipt, later SDK checkpoint, redelivery, reversion and gap behavior are explicit
SDK-R07 Recover lost access independently of import 16-recover-wallet-access.md; recovery story; 09-import-wallet.md Independent proof, same wallet identity and negative lost-factor case exist
SDK-R08 Complete owner-action/agreement lifecycle through the approval business domain Wallet/ownership and operation sections; 22-approvals.md; all sensitive mutations Get/list/approve/reject, handler continuation, binding, denial, expiry and insufficient agreement are explicit without duplicate creation
SDK-R09 Align external wallet model to current network key/address Wallet model; create/import/export files No mnemonic/derived-account capability is promised
SDK-R10 Preserve owner control across browser and server runtimes Wallet/ownership; runtime matrix; authorization sections; recovery story Protected local owner runtime is allowed; no public raw share or automatic transfer to backend
SDK-R11 Preserve focused wallet descriptions and add complete grouped catalogs Smallest surface; directory inventory Seventeen focused references remain; eight foundation/grouped documents produce the complete 25-document collection
SDK-R12 Document five end-to-end stories Five complete business stories Each names integrator action, hidden SDK work and owner participation
SDK-R13 Preserve evidence/readiness boundaries Status/source boundary; every method Status section No document claims an available binding, implementation or production readiness
SDK-R14 Add frontend identity without conflating it with wallet authority Runtime matrix; 18-frontend-authentication.md; all five stories Browser/server boundary, complete auth inventory, session limits and per-action backend checks are explicit
SDK-R15 Catalog user, organization, application configuration, approval, policy and transaction domains 19-users.md24-transactions.md Verified methods, explicit absences, runtimes and negative boundaries are complete without smart-wallet scope
SDK-R16 Provide embedded wallets with Google, Apple, Telegram and passkey login Authentication and wallet mode sections All four paths are explicit; Telegram is target-owned and no identity proof grants wallet authority
SDK-R17 Provide explicit service-controlled server wallets for treasury and payouts Wallet model; create/send/policy/approval references Mode, service identity, policies and transaction limits are required; user login never grants treasury authority
SDK-R18 Support truthful v1 network-fee funding without smart wallets 06-send-transaction.md; 24-transactions.md Solana native fee payer is exact-quote bound; EVM prefunding is separate and never labelled sponsorship
SDK-R19 Send assets and receive signed deposit/transfer notifications through one facade Transfer/event contract; 06-send-transaction.md; 15-watch-wallet.md; 24-transactions.md Sender/integrity, tenant/wallet binding, correlation, dedup/checkpoint, correction and finality boundaries are explicit

Implementation gates and unknowns#

Before executable SDK work, the project must run its required specification pipeline and resolve exact backend contracts for operation states/retention, owner identity and proof, session storage/revocation, OAuth/OTP/passkey challenge handling, MFA/recovery, agreement, policy atomicity, cryptographic formats, supported network adapters, event lifecycle, bounds and errors. Positive and negative fixtures must cover retries, changed-material conflicts, denial, expiry, challenge replay, session theft/revocation, tenant isolation, disclosure safety, event redelivery, gap recovery and cross-language canonical equivalence.

These unknown mechanisms block implementation claims, not the required business semantics recorded here.