Proposed base SDK model and business stories
The wallet model, the durable operation contract and the language bindings.
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:
createWalletgetWalletgetWalletsupdateWalletsignTransactionsendTransaction(ethSendTransactionis the EVM compatibility alias)getWalletAddressBalancesimportWalletexportWalletcreatePolicyupdatePolicysignMessagesignTypedDatawatchWalletrecoverWalletAccessgetOperation
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 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.
- Same request ID and same material converges on the recorded operation/result.
- Same request ID with different material conflicts and creates no additional effect.
- Timeout, cancellation or lost response after submission means outcome unknown, not rolled back.
getOperation(requestId)recovers progress and meaningful typed result after application restart.- The SDK never generates a new identity, refreshes signed material, re-signs, rebroadcasts or rediscloses merely to escape uncertainty.
- Identity retention is finite and must be published by the implementation. Missing or expired identity is explicit and never proof that repeating a financial, signing, creation or disclosure operation is safe.
- A one-time sensitive result may be replaced by a durable disclosure disposition rather than returned again.
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.
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.
Complete grouped catalogs#
- Frontend authentication preserves the verified browser/core and React authentication inventory and includes read-only/read-write sessions, session profiles and the canonical organization OAuth-credential administration boundary.
- Users catalogs eight core user methods, four user-tag methods and eleven attached-credential methods.
- Organizations catalogs thirteen hierarchy/security methods plus two invitation methods.
- Application configuration records the verified bootstrap helper and the absence of generic application CRUD.
- Approvals exposes get/list/approve/reject without duplicating business-action creation.
- Policies catalogs all nine verified policy methods and keeps rule expressions nested.
- Transactions catalogs primary, compatibility, history and supporting transaction methods.
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.
- TypeScript uses an optional property and
undefinedfor omission. The codec omitsundefined; it rejectsnullunless the field is nullable. - Python uses a documented
UNSETsentinel for omission.Noneis reserved for an explicitly nullable field. - Go uses a pointer or a documented option wrapper when omission is meaningful. A
nilpointer means omitted only; an explicitly nullable value uses a separate presence/validity representation. Required scalar zero values are validated as values, not repurposed as omission.
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.
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);
});
operation = await sdk.send_transaction(send_input)
if operation.state == "requiresAction":
authorization = await owner_handler(operation.authorization_request)
await sdk.get_operation({"request_id": request_id, "authorization": authorization})
recovered = await sdk.get_operation({"request_id": request_id})
await sdk.watch_wallet(watch_input, application.commit_effect_and_processed_event)
operation, err := sdk.SendTransaction(ctx, sendInput)
if err != nil { return err }
if operation.State == RequiresAction {
authorization, err := ownerHandler(ctx, operation.AuthorizationRequest)
if err != nil { return err }
_, err = sdk.GetOperation(ctx, &GetOperationInput{RequestID: requestID, Authorization: authorization})
if err != nil { return err }
}
recovered, err := sdk.GetOperation(ctx, &GetOperationInput{RequestID: requestID})
if err != nil { return err }
return sdk.WatchWallet(ctx, watchInput, application.CommitEffectAndProcessedEvent)
"use client";
function WalletAction() {
const { sdk, session } = useSdk();
return <button disabled={!session} onClick={() => startAuthorizedAction(sdk)}>Continue</button>;
}
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.md–24-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.