diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dcde60e..6c9ffde 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Something in the OMS TypeScript SDK isn't working as expected +about: Something in the OMS Wallet TypeScript SDK isn't working as expected labels: bug --- diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7d7da36..b5f6007 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,8 +15,8 @@ - [ ] `pnpm test` passes - [ ] `pnpm exec tsc --noEmit` passes - [ ] `pnpm test:types` passes (if public types changed) -- [ ] `pnpm --filter @0xsequence/oms-wallet-wagmi-connector test` passes (if connector behavior changed) -- [ ] `pnpm --filter @0xsequence/oms-wallet-wagmi-connector build` passes (if connector types/build changed) +- [ ] `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector test` passes (if connector behavior changed) +- [ ] `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector build` passes (if connector types/build changed) - [ ] Relevant example builds pass: `pnpm build:example`, `pnpm build:trails-actions-example`, `pnpm build:wagmi-example`, `pnpm build:node-example`, or `pnpm build:node-contract-deploy-example` - [ ] `pnpm build` succeeds (if touching exports, package output, or release behavior) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a56b101..99b0d2e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,36 +29,5 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Check package versions - run: pnpm check:package-versions - - - name: Typecheck - run: pnpm exec tsc --noEmit - - - name: Run tests - run: pnpm test - - - name: Build SDK - run: pnpm build - - - name: Build OMS Wallet wagmi connector - run: pnpm --filter @0xsequence/oms-wallet-wagmi-connector build - - - name: Test OMS Wallet wagmi connector - run: pnpm --filter @0xsequence/oms-wallet-wagmi-connector test - - - name: Build React example - run: pnpm build:example - env: - VITE_OMS_PUBLISHABLE_KEY: pk_ci_sdbx_ciproject_cikey - - - name: Build Trails Actions example - run: pnpm build:trails-actions-example - env: - VITE_OMS_PUBLISHABLE_KEY: pk_ci_sdbx_ciproject_cikey - - - name: Build Wagmi example - run: pnpm --filter wagmi-example build - env: - VITE_OMS_PUBLISHABLE_KEY: pk_ci_sdbx_ciproject_cikey - VITE_TRAILS_API_KEY: ci-trails-key + - name: Verify + run: pnpm verify diff --git a/AGENTS.md b/AGENTS.md index 97a1ebc..80e9620 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,58 +6,27 @@ instructions. --- -## Behavioral Guidelines +## Working Principles -Behavioral guidelines to reduce common LLM coding mistakes. (Adapted from Andrej Karpathy's -[CLAUDE.md](https://github.com/multica-ai/andrej-karpathy-skills/blob/main/CLAUDE.md).) - -**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. - -### 1. Think Before Coding -**Don't assume. Don't hide confusion. Surface tradeoffs.** -- State your assumptions explicitly. If uncertain, ask. -- If multiple interpretations exist, present them — don't pick silently. -- If a simpler approach exists, say so. Push back when warranted. -- If something is unclear, stop. Name what's confusing. Ask. - -### 2. Simplicity First -**Minimum code that solves the problem. Nothing speculative.** -- No features beyond what was asked. -- No abstractions for single-use code. -- No "flexibility" or "configurability" that wasn't requested. -- If you write 200 lines and it could be 50, rewrite it. - -Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. - -### 3. Surgical Changes -**Touch only what you must. Clean up only your own mess.** -- Don't "improve" adjacent code, comments, or formatting. -- Don't refactor things that aren't broken. -- Match existing style, even if you'd do it differently. -- Remove imports/variables YOUR changes made unused; leave pre-existing dead code unless asked. - -The test: every changed line should trace directly to the request. - -### 4. Goal-Driven Execution -**Define success criteria. Loop until verified.** -- "Add validation" → "Write tests for invalid inputs, then make them pass." -- "Fix the bug" → "Write a test that reproduces it, then make it pass." - -For multi-step tasks, state a brief plan with a verify step for each item. +- State assumptions when ambiguity affects implementation, public API, security, or release behavior. +- Keep changes surgical and traceable to the request. Avoid speculative abstractions, broad refactors, and formatting churn. +- Preserve user work in the tree and match the local style of the files you touch. +- Define success criteria for non-trivial work and choose verification proportional to the risk. --- ## Third-Party Library Docs -For **any third-party library** (`viem`, `vitest`, etc.), use the **context7** MCP to fetch -up-to-date documentation rather than relying on training data, which lags real library APIs. If -the context7 MCP server is not available, set it up: https://context7.com/install +For non-trivial or version-sensitive third-party library questions (`viem`, `vitest`, etc.), +prefer context7 or official documentation over training-data recall. If context7 is unavailable, +use official docs or local package types and note the fallback; do not block ordinary repo work just +to install extra tooling. --- ## Project Overview -This repository is a pnpm workspace for the OMS TypeScript SDK. The root package exports the `@0xsequence/typescript-sdk` library used by the React and Node examples. The SDK covers wallet authentication, OIDC redirect auth, signed WaaS requests, wallet/session storage, transaction submission, signing, access management, and indexer balance queries. +This repository is a pnpm workspace for the OMS Wallet TypeScript SDK. The root package exports the `@polygonlabs/oms-wallet` library used by the React and Node examples. The SDK covers wallet authentication, OIDC redirect auth, signed WaaS requests, wallet/session storage, transaction submission, signing, access management, and indexer balance queries. ## Setup and Tooling @@ -69,40 +38,49 @@ This repository is a pnpm workspace for the OMS TypeScript SDK. The root package ## Repository Layout - `src/index.ts`: Public SDK export surface. Keep public API changes intentional and reflected in docs and type tests when applicable. -- `src/omsClient.ts`: Top-level `OMSClient` composition for wallet and indexer clients. +- `src/omsWallet.ts`: Top-level `OMSWallet` composition for wallet and indexer clients. - `src/clients/walletClient.ts`: Main wallet/auth/signing/transaction/access implementation. - `src/clients/indexerClient.ts`: Indexer balance client and HTTP error wrapping. - `src/generated/waas.gen.ts`: Generated WaaS client and types. - `src/credentialSigner.ts`, `src/signedFetch.ts`, `src/storageManager.ts`: Credential, request-signing, and persistence boundaries. - `src/utils/` and `src/types/`: Shared SDK helpers and exported type definitions. -- `packages/oms-wallet-wagmi-connector/`: ESM-only `@0xsequence/oms-wallet-wagmi-connector` package for using an - active OMS client as a wagmi connector. +- `packages/oms-wallet-wagmi-connector/`: ESM-only `@polygonlabs/oms-wallet-wagmi-connector` package for using an + active OMS Wallet SDK instance as a wagmi connector. - `tests/`: Vitest coverage for wallet, OIDC, transactions, signing, access, indexer, and errors. - `type-tests/`: Compile-time API tests. - `examples/react/`: Vite React demo that consumes the SDK through the workspace. +- `examples/custom-google-redirect/`: Local-only Vite React demo for Google as a custom OIDC provider with a localhost redirect URI. - `examples/wagmi/`: Vite React wagmi demo using the OMS Wallet connector and MetaMask connector. - `examples/trails-actions/`: Vite React demo for Trails swap, Earn deposit, and Earn withdrawal flows. - `examples/node/`: Interactive Node OTP/signing example. - `examples/node-contract-deploy-example/`: Interactive Node ERC-20 deployment example. - `examples/shared/`: Shared browser-example design tokens, base styles, components, utilities, and Vite aliases. +- `docs/error-contracts.md`: Public error contract matrix and expectations. +- `docs/session-expiry-flow.md`: Session expiry, reauthentication, and related wallet behavior notes. - `scripts/write-esm-package.cjs`: Writes `dist/esm/package.json` during the root build. +- `scripts/check-public-api.cjs`: Compares built public declarations with the committed baseline and rejects generated WaaS type leaks. ## Commands - `pnpm install --frozen-lockfile`: Install dependencies in CI-compatible mode. -- `pnpm check:package-versions`: Verify publishable workspace package versions match and connector SDK references use `workspace:*`. +- `pnpm check:package-versions`: Verify publishable workspace package versions match, allow exact stable or prerelease semver, and require the connector SDK peer to use `workspace:^` and dev dependency to use `workspace:*`. +- `pnpm check:stable-package-versions`: Verify publishable workspace package versions match and are exact stable semver for stable releases. +- `pnpm check:public-api`: Compare built declarations with the committed baseline and reject generated WaaS type leaks. +- `pnpm verify`: Run the full SDK verification suite, including package, test, example, and publishable-artifact checks. - `pnpm exec tsc --noEmit`: Typecheck SDK source. - `pnpm test`: Run Vitest and type tests. - `pnpm test:types`: Compile `type-tests/oidcProviderTypes.ts`; useful for public type/API changes. - `pnpm build`: Build CJS and ESM SDK output under `dist/`. -- `pnpm --filter @0xsequence/oms-wallet-wagmi-connector build`: Build the wagmi connector package. -- `pnpm --filter @0xsequence/oms-wallet-wagmi-connector test`: Run the wagmi connector package tests. +- `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector build`: Build the wagmi connector package. +- `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector test`: Run the wagmi connector package tests. - `pnpm build:example`: Build the React example for Vite/GitHub Pages output after `pnpm build` has produced SDK output. +- `pnpm build:custom-google-redirect-example`: Build the local-only custom Google redirect React example. - `pnpm build:trails-actions-example`: Build the Trails Actions React example. - `pnpm build:wagmi-example`: Build the wagmi React example. - `pnpm build:node-example`: Typecheck the Node example. - `pnpm build:node-contract-deploy-example`: Typecheck the Node contract deploy example. - `pnpm dev:example`: Start the React demo dev server. +- `pnpm dev:custom-google-redirect-example`: Start the local custom Google redirect React demo dev server on port `5173`. - `pnpm dev:trails-actions-example`: Start the Trails Actions React demo dev server. - `pnpm dev:wagmi-example`: Start the wagmi React demo dev server. - `pnpm dev:node-example`: Run the interactive Node OTP example. @@ -111,31 +89,37 @@ This repository is a pnpm workspace for the OMS TypeScript SDK. The root package ## Verification Workflow +For README/API/docs-only edits, use source-backed spot checks plus `git diff --check`; run compilers +or example builds only when the docs claim changed source behavior, public API shape, or runnable +example code. + 1. Run the smallest relevant Vitest file or type check for the changed behavior. 2. Run `pnpm test` for SDK behavior changes. 3. Run `pnpm exec tsc --noEmit` before handing off source or public type changes. 4. Run `pnpm test:types` directly when changing public generics, overloads, exported types, OIDC provider typing, or `src/index.ts`. -5. Run `pnpm --filter @0xsequence/oms-wallet-wagmi-connector test` and `pnpm --filter @0xsequence/oms-wallet-wagmi-connector build` when changing the wagmi connector package. +5. Run `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector test` and `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector build` when changing the wagmi connector package. 6. Run `pnpm build:node-example` when SDK exports, module resolution, or Node example usage changes. 7. Run `pnpm build` before release/build-output work, package entrypoint changes, or React example builds from a clean tree. -8. Run `pnpm build:example` after `pnpm build` when changing the React example, Vite config, public browser API shape, or Pages deployment assumptions. -9. Run `pnpm build:trails-actions-example` after `pnpm build` when changing the Trails Actions example, shared browser example utilities, or Pages deployment assumptions. -10. Run `pnpm build:wagmi-example` after `pnpm build` when changing the wagmi example, connector browser usage, or Pages deployment assumptions. -11. Run `pnpm build:node-contract-deploy-example` when SDK exports, transaction APIs, module resolution, or the Node contract deploy example changes. +8. Run `pnpm check:public-api` after `pnpm build` when changing root exports or public declarations. +9. Run `pnpm build:example` after `pnpm build` when changing the React example, Vite config, public browser API shape, or Pages deployment assumptions. +10. Run `pnpm build:custom-google-redirect-example` when changing the custom Google redirect example, OIDC redirect provider configuration, or browser callback assumptions. +11. Run `pnpm build:trails-actions-example` after `pnpm build` when changing the Trails Actions example, shared browser example utilities, or Pages deployment assumptions. +12. Run `pnpm build:wagmi-example` after `pnpm build` when changing the wagmi example, connector browser usage, or Pages deployment assumptions. +13. Run `pnpm build:node-contract-deploy-example` when SDK exports, transaction APIs, module resolution, or the Node contract deploy example changes. ## Coding and Architecture Rules - Source files under `src/` use explicit `.js` extensions in relative imports so emitted JavaScript resolves correctly. Preserve that pattern in SDK source. -- Treat `src/index.ts` as the public API gate. Export new public types or clients there intentionally, and update `API.md`, `README.md`, and type tests when public behavior changes. +- Treat `src/index.ts` and exported types as the public API gate. Export new public types or clients intentionally, and update `API.md`, `README.md`, and type tests when public behavior changes. - Route wallet API calls through `WalletClient`, generated WaaS types, `createSignedFetch`, and `CredentialSigner` instead of duplicating signing or header logic. - Use `StorageManager` abstractions for persistence-sensitive code. Browser storage and memory fallback behavior are part of the SDK contract. -- Preserve typed SDK error classes and `toOmsSdkError` behavior when wrapping network, generated-client, validation, session, and transaction-status failures. -- Keep supported network metadata and chain ID lookup going through `src/networks.ts`, `Networks`, `supportedNetworks`, `findNetworkById`, and `findNetworkByName` instead of ad hoc conversion. +- Preserve typed SDK error classes and `toOMSWalletError` behavior when wrapping network, generated-client, validation, session, and transaction-status failures. +- Keep supported network metadata and chain ID lookup going through `src/networks.ts`, `Networks`, `findNetworkById`, and `findNetworkByName` instead of ad hoc conversion. - The TypeScript compiler is the enforced style gate. There is no separate lint or formatter command in the root scripts, so avoid broad formatting churn and match the local file style. ## Example App Styling -- The browser examples (`examples/react`, `examples/wagmi`, `examples/trails-actions`) share one set of design tokens in `examples/shared/oms-tokens.css`, mirrored from `oms-sdk-design-system`'s `omsTokens`. Each example's `styles.css` imports it via `@import url("../../shared/oms-tokens.css")`. +- The browser examples (`examples/react`, `examples/custom-google-redirect`, `examples/wagmi`, `examples/trails-actions`) share one set of design tokens in `examples/shared/oms-tokens.css`, mirrored from `oms-sdk-design-system`'s `omsTokens`. Each example's `styles.css` imports it via `@import url("../../shared/oms-tokens.css")`. - Reference the `--oms-*` CSS variables (colors, radius, typography, focus rings) for any example styling. Do not hardcode new hex/radius values in the per-app `styles.css` files; if a token is missing, add it to `examples/shared/oms-tokens.css` so all examples stay in sync. (The `.burn-button` fire gradient in the React example is an intentional decorative-effect exception, not a token.) - When tokens change in `oms-sdk-design-system`, update `examples/shared/oms-tokens.css` to match rather than editing each example. @@ -146,32 +130,30 @@ execution commands. ### Testing Guidance -- Test promises, not implementation. Use `Promise -> Risk -> Evidence -> Cost -> Action` for non-trivial changes. -- Prefer the lowest reliable evidence level: TypeScript checks for impossible states, Vitest tests for SDK behavior, type tests for public API constraints, and example builds for consumer compatibility. -- Existing tests stub network boundaries while asserting public wallet/indexer behavior, request payloads, error mapping, OIDC state handling, pagination, transaction status behavior, and type-level API promises. Keep that behavior focus. -- Some existing tests seed private wallet state through `(wallet as any)`. Use that only as local compatibility in existing test areas; prefer public methods or small fixtures for new coverage. -- Bug fixes should include regression evidence when feasible. -- For auth, signing, transaction execution, access revocation, storage persistence, and error classification, add focused tests that would fail if the externally visible promise breaks. +- Use `TESTING.md` as the source of truth for test boundaries and public error contract rules. +- Test promises, not implementation. Choose the cheapest reliable evidence for the risk. +- Prefer TypeScript checks for impossible states, Vitest tests for SDK behavior, type tests for public API constraints, and example builds for consumer compatibility. +- For auth, signing, transaction execution, access revocation, storage persistence, and error classification, add focused regression tests when externally visible behavior changes. ### Public Error Contract Tests -- Follow the detailed rules in `TESTING.md` before adding or updating public error contract tests. -- Exercise real public runtime APIs and mock only external boundaries. +- Follow `TESTING.md` before adding or updating public error contract tests. +- Exercise public runtime APIs and mock only external boundaries. - Snapshot stable public fields only; do not snapshot raw `cause`, stacks, generated internals, headers, timestamps, or full backend payloads. -- Snapshot changes are not automatically regressions. First decide whether the new error shape is the intended public contract, then either update the snapshot or fix the implementation. ## Generated Files and External Artifacts - `src/generated/waas.gen.ts` is generated by Webrpc and marked `DO NOT EDIT`. Update the generated-client source of truth rather than hand-editing this file as normal source. - The generated WaaS header records the upstream schema path and generation command. This repo does not currently include that schema; if regenerating the client, document the schema source and exact command used. -- The wagmi connector's SDK peer dependency is intentionally `workspace:*` in source. Release with pnpm so the published package gets the exact SDK version; do not hand-edit that peer to a literal version. +- The wagmi connector's SDK peer dependency is intentionally `workspace:^` in source and its SDK dev dependency is intentionally `workspace:*`. Release with pnpm so the published peer gets the compatible release range; do not hand-edit that peer to a literal version. - `pnpm-lock.yaml` is the dependency lockfile. Update it through pnpm, not by hand. -- `dist/`, `examples/react/dist/`, `examples/wagmi/dist/`, and `*.tsbuildinfo` files are build outputs and should not be edited as source. +- `dist/`, `examples/react/dist/`, `examples/custom-google-redirect/dist/`, `examples/wagmi/dist/`, `examples/trails-actions/dist/`, and `*.tsbuildinfo` files are build outputs and should not be edited as source. ## Security and Configuration - Do not commit real secrets. `.env.local` and `.env.*.local` files are ignored for local overrides. - The React example uses `examples/react/.env.example` for `VITE_OMS_PUBLISHABLE_KEY`; keep local overrides in `examples/react/.env.local`. +- The custom Google redirect example uses `examples/custom-google-redirect/.env.example` for `VITE_OMS_PUBLISHABLE_KEY`; keep local overrides in `examples/custom-google-redirect/.env.local`. - The wagmi React example uses `examples/wagmi/.env.example` for `VITE_OMS_PUBLISHABLE_KEY`; keep local overrides in `examples/wagmi/.env.local`. - The Trails Actions example uses `examples/trails-actions/.env.example` for `VITE_OMS_PUBLISHABLE_KEY`; keep local overrides in `examples/trails-actions/.env.local`. - The Node contract deploy example uses `examples/node-contract-deploy-example/.env.example` for `OMS_PUBLISHABLE_KEY`; keep local overrides in `examples/node-contract-deploy-example/.env.local`. @@ -202,12 +184,12 @@ execution commands. | When this changes… | Also update… | |---|---| -| Public API in `src/index.ts` | `API.md`, `README.md`, `type-tests/oidcProviderTypes.ts` | +| Public API exported through `src/index.ts` or exported public types | `API.md`, `README.md`, `type-tests/oidcProviderTypes.ts` | | Test commands (`package.json` scripts) | `TESTING.md`, `.github/workflows/tests.yml`, `AGENTS.md` Commands section | | Node or pnpm version | `.nvmrc`, `package.json#packageManager`, `.github/workflows/*.yml` | -| New third-party dependency | `package.json`, `pnpm-lock.yaml`, context7 instruction in `AGENTS.md` | +| New third-party dependency | `package.json`, `pnpm-lock.yaml`, third-party docs guidance in `AGENTS.md` | | Publishable package versioning or workspace peer protocol | `PUBLISHING.md`, `scripts/check-package-versions.cjs`, `pnpm-lock.yaml` | | `src/generated/waas.gen.ts` (regenerated) | Document schema source + regen command in PR description | | Repo structure (new top-level dirs) | `AGENTS.md` Repository Layout section | -| Examples added or renamed | `pnpm-workspace.yaml`, root `package.json` scripts, `pages.yml` | +| Examples added or renamed | `pnpm-workspace.yaml`, root `package.json` scripts, `pages.yml` when deployed | | Design tokens (`oms-sdk-design-system`) | `examples/shared/oms-tokens.css` (single source; examples import it — never hardcode hex/radius in per-app `styles.css`) | diff --git a/API.md b/API.md index ee1a5aa..2234cb3 100644 --- a/API.md +++ b/API.md @@ -1,1956 +1,1445 @@ -# OMS SDK — API Reference - -## Table of Contents - -- [OMSClient](#omsclient) - - [Constructor](#constructor) - - [supportedNetworks](#supportednetworks) -- [WalletClient](#walletclient) - - [walletAddress](#walletaddress) - - [session](#session) - - [onSessionExpired](#onsessionexpired) - - [startEmailAuth](#startemailauth) - - [completeEmailAuth](#completeemailauth) - - [startOidcRedirectAuth](#startoidcredirectauth) - - [completeOidcRedirectAuth](#completeoidcredirectauth) - - [signInWithOidcRedirect](#signinwithoidcredirect) - - [signOut](#signout) - - [listWallets](#listwallets) - - [useWallet](#usewallet) - - [createWallet](#createwallet) - - [getIdToken](#getidtoken) - - [signMessage](#signmessage) - - [signTypedData](#signtypeddata) - - [isValidMessageSignature](#isvalidmessagesignature) - - [isValidTypedDataSignature](#isvalidtypeddatasignature) - - [getTransactionStatus](#gettransactionstatus) - - [sendTransaction](#sendtransaction) - - [Native token transfer](#native-token-transfer) - - [Raw data transaction](#raw-data-transaction) - - [ABI-encoded contract call](#abi-encoded-contract-call) - - [callContract](#callcontract) - - [listAccess](#listaccess) - - [listAccessPages](#listaccesspages) - - [revokeAccess](#revokeaccess) -- [IndexerClient](#indexerclient) - - [getBalances](#getbalances) - - [getTransactionHistory](#gettransactionhistory) -- [Errors](#errors) -- [Types](#types) - - [Network](#network) - - [OmsAuthConfig](#omsauthconfig) - - [OidcProviderConfig](#oidcproviderconfig) - - [OIDC Provider Helpers](#oidc-provider-helpers) - - [Auth Method Types](#auth-method-types) - - [StorageManager](#storagemanager) - - [Storage Helpers](#storage-helpers) - - [CredentialSigner](#credentialsigner) - - [Credential Signing Helpers](#credential-signing-helpers) - - [Session Listener Types](#session-listener-types) - - [Signing and Validation Method Types](#signing-and-validation-method-types) - - [OmsWallet](#omswallet) - - [PendingWalletSelection](#pendingwalletselection) - - [WalletSelectionBehavior](#walletselectionbehavior) - - [WalletCredential](#walletcredential) - - [AccessGrant](#accessgrant) - - [ListAccessParams](#listaccessparams) - - [AccessGrantPage](#accessgrantpage) - - [WalletActivationResult](#walletactivationresult) - - [Native Transaction Parameters](#native-transaction-parameters) - - [Raw Data Transaction Parameters](#raw-data-transaction-parameters) - - [ABI-Encoded Transaction Parameters](#abi-encoded-transaction-parameters) - - [SendTransactionResponse](#sendtransactionresponse) - - [TransactionStatusResponse](#transactionstatusresponse) - - [TransactionStatusPollingOptions](#transactionstatuspollingoptions) - - [TransactionMode](#transactionmode) - - [TransactionStatus](#transactionstatus) - - [FeeOptionSelector](#feeoptionselector) - - [FeeOption](#feeoption) - - [FeeOptionSelection](#feeoptionselection) - - [FeeOptionWithBalance](#feeoptionwithbalance) - - [GetBalancesParams](#getbalancesparams) - - [GetTransactionHistoryParams](#gettransactionhistoryparams) - - [IndexerNetworkType](#indexernetworktype) - - [ContractVerificationStatus](#contractverificationstatus) - - [MetadataOptions](#metadataoptions) - - [SortBy](#sortby) - - [BalancesResult](#balancesresult) - - [TransactionHistoryResult](#transactionhistoryresult) - - [Transaction](#transaction) - - [TransactionTransfer](#transactiontransfer) - - [TokenBalancesPage](#tokenbalancespage) - - [TokenBalance](#tokenbalance) - - [TokenContractInfo](#tokencontractinfo) - - [TokenMetadata](#tokenmetadata) - - [TokenMetadataAsset](#tokenmetadataasset) - - [Contract Call Arguments](#contract-call-arguments) - - [AuthMode](#authmode) - - [WalletType](#wallettype) + ---- +# TypeScript API reference -## OMSClient +## OMSWallet -The top-level entry point for the SDK. +### `OMSWallet` ```typescript -import { OMSClient } from '@0xsequence/typescript-sdk' - -const oms = new OMSClient({ - publishableKey: 'your-publishable-key', -}) +export declare class OMSWallet { + readonly wallet: OMSWalletClient; + readonly indexer: OMSWalletIndexerClient; + constructor(params: OMSWalletParams); +} ``` -### Constructor +### `OMSWalletParams` ```typescript -new OMSClient(params: { - publishableKey: string - auth?: OmsAuthConfig - storage?: StorageManager - redirectAuthStorage?: StorageManager - credentialSigner?: CredentialSigner -}) +export interface OMSWalletParams { + publishableKey: string; + storage?: StorageManager; + redirectAuthStorage?: StorageManager; + credentialSigner?: CredentialSigner; +} ``` -**Parameters** - -| Name | Type | Required | Description | -|---|---|---|---| -| `publishableKey` | `string` | Yes | Your OMS publishable key. | -| `auth` | `OmsAuthConfig` | No | OIDC provider configuration. Defaults to the built-in Google and Apple providers. | -| `storage` | `StorageManager` | No | Storage backend for wallet metadata. Defaults to `LocalStorageManager` when browser `localStorage` is available, otherwise `MemoryStorageManager`. | -| `redirectAuthStorage` | `StorageManager` | No | Transient storage for OIDC redirect auth state. Defaults to `sessionStorage` when available. | -| `credentialSigner` | `CredentialSigner` | No | Request credential signer. Defaults to a non-extractable WebCrypto P-256 signer (`ecdsa-p256-sha256`) where WebCrypto is available. | - -**Properties** +## Authentication and sessions -| Name | Type | Description | -|---|---|---| -| `wallet` | `WalletClient` | Handles authentication, signing, and transactions. | -| `indexer` | `IndexerClient` | Queries on-chain state and token balances. | -| `supportedNetworks` | `readonly Network[]` | Networks configured by the SDK. Same value as the exported `supportedNetworks`. | - -### supportedNetworks +### `OMSWalletClient.walletAddress` ```typescript -oms.supportedNetworks: readonly Network[] +readonly walletAddress: Address | undefined; ``` -Returns the supported network registry. Each entry has `id`, `name`, `nativeTokenSymbol`, `explorerUrl`, and `displayName`. - -## WalletClient - -Accessed via `oms.wallet`. Manages the full wallet lifecycle: authentication, session persistence, signing, and transaction submission. - -### walletAddress +### `OMSWalletClient.session` ```typescript -walletAddress: Address | undefined +readonly session: OMSWalletSessionState; ``` -The on-chain address of the active wallet (`Address` is the viem/abitype hex address type). Undefined until email or OIDC auth completes successfully, or a persisted session is restored. - -### session +### `OMSWalletClient.onSessionExpired` ```typescript -type OMSClientSessionLoginType = 'email' | 'google-auth' | 'oidc' - -interface OMSClientSessionState { - walletAddress: Address | undefined - expiresAt: string | undefined - loginType: OMSClientSessionLoginType | undefined - sessionEmail: string | undefined -} - -interface OMSClientSessionExpiredEvent { - session: OMSClientSessionState - expiredAt: string -} - -wallet.session: OMSClientSessionState +onSessionExpired(listener: OMSWalletSessionExpiredListener): () => void; ``` -Completed wallet sessions persist `walletAddress`, credential expiry, login type, and returned email in the configured `storage`. Pending email OTP and OIDC redirect state are not exposed through `session`; use the auth method results to drive pending UI. - -Expired sessions are made inactive before protected wallet operations and throw `OmsSessionError` with code `OMS_SESSION_EXPIRED`. The SDK clears the active signer/session state, but keeps the expired session metadata in storage until the app explicitly starts a new auth flow or calls `signOut()`. Use `wallet.onSessionExpired` to update app state or route back to sign-in; the event includes the expired session snapshot so apps can reuse `sessionEmail` for email OTP reauth or provider-specific account hints, including Google `loginHint`, after a page refresh. - -### onSessionExpired +### `OMSWalletClient.startEmailAuth` ```typescript -const unsubscribe = wallet.onSessionExpired((event) => { - showReauth(event.session) -}) +startEmailAuth(params: StartEmailAuthParams): Promise; ``` -Registers a listener for expired wallet sessions and returns an unsubscribe function. The wallet client stores the latest expired-session event and replays it to each new listener until a new auth flow, new wallet session, or `signOut()` clears it. - ---- - -### startEmailAuth +### `OMSWalletClient.completeEmailAuth` ```typescript -startEmailAuth(params: { email: string }): Promise +completeEmailAuth(params: Omit & { + walletSelection: "manual"; +}): Promise; +completeEmailAuth(params: Omit & { + walletSelection?: "automatic"; +}): Promise; +completeEmailAuth(params: CompleteEmailAuthParams): Promise; ``` -Sends a one-time passcode to the provided email address to begin authentication. If a wallet session is already active, it is cleared before the new auth attempt starts. - -After this resolves, display an OTP input and pass the code to [`completeEmailAuth`](#completeemailauth). - -**Parameters** - -| Name | Type | Description | -|---|---|---| -| `email` | `string` | The email address to send the one-time passcode to. | - -**Returns** `Promise` - -**Throws** if the network request fails or the email is invalid. - -**Example** +### `OMSWalletClient.signInWithOidcIdToken` ```typescript -await oms.wallet.startEmailAuth({ email: 'user@example.com' }) +signInWithOidcIdToken(params: Omit & { + walletSelection: "manual"; +}): Promise; +signInWithOidcIdToken(params: Omit & { + walletSelection?: "automatic"; +}): Promise; +signInWithOidcIdToken(params: SignInWithOidcIdTokenParams): Promise; ``` ---- - -### completeEmailAuth +### `OMSWalletClient.startOidcRedirectAuth` ```typescript -completeEmailAuth(params: { - code: string - walletType?: WalletType - walletSelection?: 'automatic' | 'manual' - sessionLifetimeSeconds?: number -}): Promise< - | { walletAddress: Address; wallet: OmsWallet; wallets: OmsWallet[]; credential: WalletCredential } - | PendingWalletSelection -> +startOidcRedirectAuth(params: StartOidcRedirectAuthParams): Promise; ``` -Verifies the OTP code and activates a wallet. Must be called after [`startEmailAuth`](#startemailauth). - -This method verifies the code with a one-week session lifetime by default, loads all wallet pages, then automatically selects an existing wallet matching `walletType`, or creates a new one if none exists. Wallet metadata is persisted to storage. Pass `sessionLifetimeSeconds` to request a shorter or longer session lifetime. Pass `walletSelection: 'manual'` to return a [`PendingWalletSelection`](#pendingwalletselection) bound to the verified auth flow; complete selection through that object. - -**Parameters** - -| Name | Type | Required | Description | -|---|---|---|---| -| `code` | `string` | Yes | The one-time passcode entered by the user. | -| `walletType` | `WalletType` | No | The wallet type to load or create. Defaults to `WalletType.Ethereum`. | -| `walletSelection` | `'automatic' \| 'manual'` | No | Defaults to `'automatic'`. Set to `'manual'` to let the app choose an existing wallet or create one through the returned pending selection. | -| `sessionLifetimeSeconds` | `number` | No | Requested session lifetime in seconds. Defaults to one week. | - -**Returns** `Promise<{ walletAddress: Address; wallet: OmsWallet; wallets: OmsWallet[]; credential: WalletCredential }>` by default, or `Promise` when `walletSelection` is `'manual'`. - -**Throws** if the code is incorrect, expired, or the network request fails. - -**Example** +### `OMSWalletClient.completeOidcRedirectAuth` ```typescript -try { - const { walletAddress, credential } = await oms.wallet.completeEmailAuth({ code: '123456' }) - console.log('Wallet ready:', walletAddress, credential.credentialId) -} catch (err) { - // Handle wrong or expired code -} +completeOidcRedirectAuth(): Promise; +completeOidcRedirectAuth(params: Omit & { + walletSelection: "manual"; +}): Promise; +completeOidcRedirectAuth(params: Omit & { + walletSelection?: "automatic"; +}): Promise; +completeOidcRedirectAuth(params: CompleteOidcRedirectAuthParams): Promise; ``` -Manual selection: +### `OMSWalletClient.signInWithOidcRedirect` ```typescript -const selection = await oms.wallet.completeEmailAuth({ - code: '123456', - walletType: WalletType.Ethereum, - walletSelection: 'manual', -}) - -await selection.selectWallet({ walletId: selection.wallets[0].id }) -// or: -await selection.createAndSelectWallet({ reference: 'main' }) +signInWithOidcRedirect(params: SignInWithOidcRedirectParams): Promise; ``` ---- - -### startOidcRedirectAuth +### `OMSWalletClient.signOut` ```typescript -startOidcRedirectAuth(params: { - provider: string | OidcProviderConfig - redirectUri?: string - walletType?: WalletType - walletSelection?: 'automatic' | 'manual' - sessionLifetimeSeconds?: number - relayRedirectUri?: string - authorizeParams?: Record - loginHint?: string -}): Promise<{ url: string; state: string; challenge: string }> +signOut(): Promise; ``` -Starts an OIDC authorization-code redirect flow and returns the provider authorization URL. If a wallet session is already active, it is cleared before the new auth attempt starts. The SDK stores transient redirect auth state so the callback can complete after a full-page redirect with the same credential signer, credential id, and signing algorithm. - -If `provider` is a string, it must match a configured `auth.oidcProviders` key. Passing an `OidcProviderConfig` object directly is also supported. - -In browser environments, `redirectUri` defaults to the current page URL without query or hash. Outside a browser, pass `redirectUri`. - -When `relayRedirectUri` is set, the provider redirects through that relay before returning to the app `redirectUri`. - -Pass `walletSelection` or `sessionLifetimeSeconds` at start to store completion preferences in the pending redirect state. `completeOidcRedirectAuth` uses those stored values after the provider redirects back unless completion params override them. - -Pass `loginHint` for Google redirect flows to set the Google `login_hint` authorization parameter, which can prefill or select the expected account. The SDK only sends `login_hint` for providers whose issuer is `https://accounts.google.com`. If omitted, the SDK falls back to the previous active session email when one exists before the redirect auth attempt starts. After `signOut()`, that previous session email is cleared. To force no `login_hint` for a call, pass `loginHint: ''`. +### `OMSWalletClient.listWallets` ```typescript -const { url } = await oms.wallet.startOidcRedirectAuth({ - provider: 'google', - redirectUri: `${window.location.origin}/auth/callback`, -}) - -window.location.assign(url) +listWallets(): Promise>; ``` ---- - -### completeOidcRedirectAuth +### `OMSWalletClient.useWallet` ```typescript -completeOidcRedirectAuth(params: { - callbackUrl?: string - cleanUrl?: boolean - replaceUrl?: (url: string) => void - walletSelection?: 'automatic' | 'manual' - sessionLifetimeSeconds?: number -} = {}): Promise< - | { walletAddress: Address; wallet: OmsWallet; wallets: OmsWallet[]; credential: WalletCredential } - | PendingWalletSelection - | void -> +useWallet(params: { + walletId: string; +}): Promise; ``` -Completes an OIDC redirect flow by validating the callback, completing auth with a one-week session lifetime by default, and activating an existing wallet or creating one. Completion must run with the same credential signer, credential id, and signing algorithm that started the redirect. In browser environments, `callbackUrl` defaults to `window.location.href`; if the current URL has no OIDC callback params, the method returns `undefined` without requiring pending redirect storage. - -Pass `sessionLifetimeSeconds` to request a shorter or longer session lifetime. Pass `walletSelection: 'manual'` to return a [`PendingWalletSelection`](#pendingwalletselection) for app-driven wallet selection. If omitted, completion uses values stored by `startOidcRedirectAuth` or `signInWithOidcRedirect`, then falls back to automatic wallet selection and the default one-week lifetime. - -When `callbackUrl` is omitted, OAuth query parameters are cleaned by default. Explicit `callbackUrl` calls clean only when `cleanUrl: true`; outside a browser, pass `replaceUrl` when cleaning. +### `OMSWalletClient.createWallet` ```typescript -const result = await oms.wallet.completeOidcRedirectAuth() -if (result) { - console.log(result.walletAddress) -} +createWallet(params?: { + type?: WalletType; + reference?: string; +}): Promise; ``` ---- - -### signInWithOidcRedirect +### `OMSWalletClient.getIdToken` ```typescript -signInWithOidcRedirect(params: { - provider: string | OidcProviderConfig - redirectUri?: string - walletType?: WalletType - walletSelection?: 'automatic' | 'manual' - relayRedirectUri?: string - authorizeParams?: Record - loginHint?: string - sessionLifetimeSeconds?: number - currentUrl?: string - assignUrl?: (url: string) => void -}): Promise +getIdToken(params?: GetIdTokenParams): Promise; ``` -Browser convenience method for regular web apps. It starts OIDC redirect auth, stores pending redirect state, redirects with `window.location.assign`, and returns `void`. Use [`completeOidcRedirectAuth`](#completeoidcredirectauth) on the callback page to finish auth. - -`redirectUri` defaults to the current page URL without query or hash. Pass `currentUrl` to derive that value from a specific URL, and pass `assignUrl` outside a browser or when testing. `walletSelection` and `sessionLifetimeSeconds` are stored with the pending redirect state and used by `completeOidcRedirectAuth` after the provider redirects back. +### `OMSWalletClient.listAccess` ```typescript -void oms.wallet.signInWithOidcRedirect({ provider: 'google' }) -void oms.wallet.signInWithOidcRedirect({ provider: 'apple' }) - -const result = await oms.wallet.completeOidcRedirectAuth() -if (result) { - console.log(result.walletAddress) -} +listAccess(params?: ListAccessParams): Promise; ``` ---- - -### signOut +### `OMSWalletClient.listAccessPages` ```typescript -signOut(): Promise +listAccessPages(params?: ListAccessParams): AsyncIterable; ``` -Clears the wallet session metadata from storage and clears the active credential signer where supported. After calling this, `walletAddress` and `session` metadata are no longer available and the user must authenticate again through email auth or OIDC redirect auth. - -**Returns** `Promise` - -**Example** +### `OMSWalletClient.revokeAccess` ```typescript -await oms.wallet.signOut() +revokeAccess(params: { + targetCredentialId: string; +}): Promise; ``` ---- - -### listWallets +### `StartEmailAuthParams` ```typescript -listWallets(): Promise +export interface StartEmailAuthParams { + email: string; + sessionLifetimeSeconds?: number; +} ``` -Returns all wallets available to an authenticated active or pending wallet-selection session. - ---- - -### useWallet +### `CompleteEmailAuthParams` ```typescript -useWallet(params: { walletId: string }): Promise<{ walletAddress: Address; wallet: OmsWallet }> +export interface CompleteEmailAuthParams { + code: string; + walletType?: WalletType; + walletSelection?: WalletSelectionBehavior; +} ``` -Activates an existing wallet by server-side wallet id and persists it as the current wallet session. Requires an active wallet session; pending manual auth flows must use [`PendingWalletSelection.selectWallet`](#pendingwalletselection). - ---- - -### createWallet +### `CompleteEmailAuthResult` ```typescript -createWallet(params?: { type?: WalletType; reference?: string }): Promise<{ walletAddress: Address; wallet: OmsWallet }> +export interface CompleteEmailAuthResult { + readonly walletAddress: Address; + readonly wallet: WalletAccount; + readonly wallets: ReadonlyArray; + readonly credential: Readonly; +} ``` -Creates a new wallet, activates it, and persists it as the current wallet session. Requires an active wallet session. `type` defaults to `WalletType.Ethereum`. Pending manual auth flows must use [`PendingWalletSelection.createAndSelectWallet`](#pendingwalletselection), which uses the auth-requested wallet type automatically. - ---- - -### getIdToken +### `SignInWithOidcIdTokenParams` ```typescript -getIdToken(params?: { - ttlSeconds?: number - customClaims?: Record -}): Promise +export interface SignInWithOidcIdTokenParams { + idToken: string; + issuer: string; + audience: string; + walletType?: WalletType; + walletSelection?: WalletSelectionBehavior; + sessionLifetimeSeconds?: number; + provider?: string; + providerLabel?: string; +} ``` -Requests an ID token for the active wallet session. The SDK uses the active wallet id automatically. - -**Parameters** - -| Name | Type | Description | -|---|---|---| -| `ttlSeconds` | `number` | Optional token lifetime in seconds. | -| `customClaims` | `Record` | Optional custom claims to include in the token. | - -**Returns** `Promise` — the issued ID token. - ---- - -### signMessage +### `CompleteOidcIdTokenAuthResult` ```typescript -signMessage(params: { - network: Network - message: string -}): Promise +export interface CompleteOidcIdTokenAuthResult { + readonly walletAddress: Address; + readonly wallet: WalletAccount; + readonly wallets: ReadonlyArray; + readonly credential: Readonly; +} ``` -Signs an arbitrary message using the active wallet session credential. - -**Parameters** - -| Name | Type | Description | -|---|---|---| -| `network` | `Network` | The network for the signing context. Use an exported registry value such as `Networks.polygon`. See [Network](#network). | -| `message` | `string` | The message to sign. | - -**Returns** `Promise` — a hex-encoded signature. - -**Example** +### `StartOidcRedirectAuthParams` ```typescript -import { Networks } from '@0xsequence/typescript-sdk' -const sigFromNetwork = await oms.wallet.signMessage({ network: Networks.polygon, message: 'some message to sign' }) +export type StartOidcRedirectAuthParams = { + walletType?: WalletType; + walletSelection?: WalletSelectionBehavior; + sessionLifetimeSeconds?: number; + loginHint?: string; +} & ({ + provider: OmsRelayOidcProvider; + omsRelayReturnUri?: string; + authorizeParams?: never; +} | { + provider: CustomOidcProviderConfig; + omsRelayReturnUri?: never; + authorizeParams?: Record; +}); ``` ---- - -### signTypedData +### `StartOidcRedirectAuthResult` ```typescript -signTypedData(params: { - network: Network - typedData: any -}): Promise +export interface StartOidcRedirectAuthResult { + authorizationUrl: string; +} ``` -Signs EIP-712 typed data using the active wallet session credential. - -**Returns** `Promise` — a hex-encoded signature. - ---- - -### isValidMessageSignature +### `CompleteOidcRedirectAuthParams` ```typescript -isValidMessageSignature(params: { - network?: Network - walletAddress?: Address - walletId?: string - message: string - signature: string -}): Promise +export interface CompleteOidcRedirectAuthParams { + callbackUrl?: string; + cleanUrl?: boolean; + replaceUrl?: (url: string) => void; + walletSelection?: WalletSelectionBehavior; + sessionLifetimeSeconds?: number; +} ``` -Validates a message signature. If neither `walletAddress` nor `walletId` is provided, the active wallet session id is used when available. - ---- - -### isValidTypedDataSignature +### `CompleteOidcRedirectAuthResult` ```typescript -isValidTypedDataSignature(params: { - network?: Network - walletAddress?: Address - walletId?: string - typedData: any - signature: string -}): Promise +export interface CompleteOidcRedirectAuthResult { + readonly walletAddress: Address; + readonly wallet: WalletAccount; + readonly wallets: ReadonlyArray; + readonly credential: Readonly; +} ``` -Validates an EIP-712 typed data signature. If neither `walletAddress` nor `walletId` is provided, the active wallet session id is used when available. - ---- - -### getTransactionStatus +### `SignInWithOidcRedirectParams` ```typescript -getTransactionStatus(params: { txnId: string }): Promise +export type SignInWithOidcRedirectParams = { + walletType?: WalletType; + walletSelection?: WalletSelectionBehavior; + sessionLifetimeSeconds?: number; + loginHint?: string; +} & { + currentUrl?: string; + assignUrl?: (url: string) => void; +} & ({ + provider: OmsRelayOidcProvider; + omsRelayReturnUri?: string; + authorizeParams?: never; +} | { + provider: CustomOidcProviderConfig; + omsRelayReturnUri?: never; + authorizeParams?: Record; +}); ``` -Fetches the latest status for a prepared/executed transaction. This is useful after calling [`sendTransaction`](#sendtransaction) with `waitForStatus: false`. - ---- +### `OmsRelayOidcProviders` -### sendTransaction - -`sendTransaction` is overloaded with three signatures depending on the type of transaction. - -#### Native Token Transfer +Fixed OMS relay providers. Their OAuth configuration is not caller-editable. ```typescript -sendTransaction(params: { - network: Network - to: Address - value: bigint - mode?: TransactionMode - selectFeeOption?: FeeOptionSelector - waitForStatus?: boolean - statusPolling?: TransactionStatusPollingOptions -}): Promise +export declare const OmsRelayOidcProviders: Readonly<{ + google: OmsRelayOidcProvider<"google">; + apple: OmsRelayOidcProvider<"apple">; +}>; ``` -Sends native tokens (ETH, POL, etc.) to an address. +### `OmsRelayOidcProvider` -```typescript -import { parseUnits } from 'viem' - -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, - to: '0x1111111111111111111111111111111111111111', - value: parseUnits('1', 18), // 1 POL -}) -``` - -#### Raw Data Transaction +An opaque SDK-owned OMS relay provider value. +Obtain values from OmsRelayOidcProviders; object literals are invalid. ```typescript -sendTransaction(params: { - network: Network - to: Address - data: Hex - value?: bigint - mode?: TransactionMode - selectFeeOption?: FeeOptionSelector - waitForStatus?: boolean - statusPolling?: TransactionStatusPollingOptions -}): Promise +export interface OmsRelayOidcProvider { + readonly provider: Provider; +} ``` -Sends a transaction with arbitrary calldata as a hex string. Use this when you have pre-encoded calldata. +### `CustomOidcProviderConfig` + +A caller-owned OIDC provider configuration. ```typescript -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, - to: '0x2222222222222222222222222222222222222222', - data: '0x12345678', -}) +export interface CustomOidcProviderConfig { + readonly clientId: string; + readonly issuer: string; + readonly authorizationUrl: string; + readonly providerRedirectUri: string; + readonly provider?: string; + readonly providerLabel?: string; + readonly scopes?: readonly string[]; + readonly authorizeParams?: Readonly>; + readonly authMode?: OidcAuthMode; +} ``` -#### ABI-Encoded Contract Call +### `AuthMode` ```typescript -sendTransaction(params: { - network: Network - to: Address - abi: Abi | readonly unknown[] - functionName: string - args?: unknown[] - value?: bigint - mode?: TransactionMode - selectFeeOption?: FeeOptionSelector - waitForStatus?: boolean - statusPolling?: TransactionStatusPollingOptions -}): Promise +export declare const AuthMode: Readonly<{ + readonly OTP: "otp"; + readonly IDToken: "id-token"; + readonly AuthCode: "auth-code"; + readonly AuthCodePKCE: "auth-code-pkce"; +}>; +export type AuthMode = (typeof AuthMode)[keyof typeof AuthMode]; ``` -Sends a contract interaction with ABI encoding via viem. The calldata is encoded automatically from `abi`, `functionName`, and `args`. When `abi` is passed as a const ABI, TypeScript narrows valid `functionName` values and infers `args`. +### `OidcAuthMode` ```typescript -import { parseUnits } from 'viem' - -const erc20Abi = [ - { - name: 'transfer', - type: 'function', - inputs: [ - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, - ], - }, -] as const - -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, - to: '0x3333333333333333333333333333333333333333', - abi: erc20Abi, - functionName: 'transfer', - args: ['0x1111111111111111111111111111111111111111', parseUnits('1', 18)], -}) +export type OidcAuthMode = typeof AuthMode.AuthCode | typeof AuthMode.AuthCodePKCE; ``` -The transaction variants share these common fields. `value` is required for native token transfers and optional for raw-data and ABI-encoded contract calls. - -| Name | Type | Description | -|---|---|---| -| `value` | `bigint` | Native token value to attach (in wei). | -| `mode` | `TransactionMode` | Transaction execution mode. Defaults to `TransactionMode.Relayer`. | -| `selectFeeOption` | `FeeOptionSelector` | Optional callback for choosing a fee option. | -| `waitForStatus` | `boolean` | Set to `false` to return immediately after execute without polling transaction status. | -| `statusPolling` | `TransactionStatusPollingOptions` | Optional post-execute polling configuration. | - -**Returns** `Promise` — the prepared transaction ID, latest status, and transaction hash when available. - -**Throws** if no session is active, local validation or fee selection fails, or a prepare/execute/status request fails. On-chain failed transaction statuses are returned as transaction status responses. - -When fee options are returned, `selectFeeOption` receives `FeeOptionWithBalance[]`. -Each entry includes the `FeeOption` plus the selected wallet's balance -for that fee token when the indexer can load it. Use -`FeeOptionSelector.firstAvailable` to choose the first option the wallet can pay, -or return `option.selection` from a custom selector. - ---- - -### callContract +### `WalletType` ```typescript -callContract(params: { - network: Network - contractAddress: Address - method: string - args?: Array<{ type: string; value: unknown }> - mode?: TransactionMode - selectFeeOption?: FeeOptionSelector - waitForStatus?: boolean - statusPolling?: TransactionStatusPollingOptions -}): Promise +export declare const WalletType: Readonly<{ + readonly Ethereum: "ethereum"; +}>; +export type WalletType = (typeof WalletType)[keyof typeof WalletType]; ``` -Calls a state-changing smart contract function using a method signature string and loosely-typed argument list. For fully-typed ABI encoding, prefer the ABI overload of [`sendTransaction`](#abi-encoded-contract-call). - -**Parameters** - -| Name | Type | Required | Description | -|---|---|---|---| -| `network` | `Network` | Yes | Network identifier. See [Network](#network). | -| `contractAddress` | `Address` | Yes | Address of the target contract. | -| `method` | `string` | Yes | ABI function signature, e.g. `"transfer(address,uint256)"`. | -| `args` | `Array<{ type: string; value: unknown }>` | No | Ordered list of typed arguments. See [Contract Call Arguments](#contract-call-arguments). | -| `mode` | `TransactionMode` | No | Transaction execution mode. Defaults to `TransactionMode.Relayer`. | -| `selectFeeOption` | `FeeOptionSelector` | No | Optional callback for choosing a fee option. | -| `waitForStatus` | `boolean` | No | Set to `false` to return immediately after execute without polling transaction status. | -| `statusPolling` | `TransactionStatusPollingOptions` | No | Optional post-execute polling configuration. | - -**Returns** `Promise` — the prepared transaction ID, latest status, and transaction hash when available. - -**Example** +### `WalletSelectionBehavior` ```typescript -import { parseUnits } from 'viem' - -const tx = await oms.wallet.callContract({ - network: Networks.polygon, - contractAddress: '0x3333333333333333333333333333333333333333', - method: 'transfer(address,uint256)', - args: [ - { type: 'address', value: '0x1111111111111111111111111111111111111111' }, - { type: 'uint256', value: parseUnits('1', 18).toString() }, - ], -}) +export type WalletSelectionBehavior = "automatic" | "manual"; ``` ---- - -### listAccess +### `WalletAccount` ```typescript -listAccess(params?: ListAccessParams): Promise +export interface WalletAccount { + readonly id: string; + readonly type: WalletType; + readonly address: Address; + readonly reference?: string; +} ``` -Returns all credentials that currently have access to this wallet across all pages. - -**Returns** `Promise` — see [AccessGrant](#accessgrant). - -**Example** +### `WalletActivationResult` ```typescript -const grants = await oms.wallet.listAccess() -console.log(grants.filter(g => g.isCaller)) // current session +export interface WalletActivationResult { + readonly walletAddress: Address; + readonly wallet: WalletAccount; +} ``` ---- - -### listAccessPages +### `PendingWalletSelection` ```typescript -listAccessPages(params?: ListAccessParams): AsyncIterable +export interface PendingWalletSelection { + readonly walletType: WalletType; + readonly wallets: ReadonlyArray; + readonly credential: Readonly; + selectWallet(params: { + walletId: string; + }): Promise; + createAndSelectWallet(params?: { + reference?: string; + }): Promise; +} ``` -Yields credential pages for callers that want page-at-a-time rendering or explicit backpressure. - -**Example** +### `OMSWalletEmailSessionAuth` ```typescript -for await (const page of oms.wallet.listAccessPages({ pageSize: 25 })) { - console.log(page.grants) +export interface OMSWalletEmailSessionAuth { + readonly type: "email"; + readonly email: string; } ``` ---- - -### revokeAccess +### `OMSWalletOidcSessionAuthFlow` ```typescript -revokeAccess(params: { targetCredentialId: string }): Promise +export type OMSWalletOidcSessionAuthFlow = "redirect" | "id-token"; ``` -Permanently revokes a credential's access to this wallet. Cannot be undone. - -**Parameters** - -| Name | Type | Description | -|---|---|---| -| `targetCredentialId` | `string` | The ID of the credential to revoke. Obtain from [`listAccess`](#listaccess). | - -**Example** +### `OMSWalletOidcSessionAuth` ```typescript -const grants = await oms.wallet.listAccess() -const other = grants.find(g => !g.isCaller) -if (other) { - await oms.wallet.revokeAccess({ targetCredentialId: other.credentialId }) +export interface OMSWalletOidcSessionAuth { + readonly type: "oidc"; + readonly flow: OMSWalletOidcSessionAuthFlow; + readonly issuer: string; + readonly provider: string | undefined; + readonly providerLabel: string | undefined; + readonly email: string | undefined; } ``` ---- - -## IndexerClient - -Accessed via `oms.indexer`. Queries on-chain balances and transaction history. - -### getBalances +### `OMSWalletSessionAuth` ```typescript -getBalances(params: { - walletAddress: string - networks?: Network[] - networkType?: 'MAINNETS' | 'TESTNETS' | 'ALL' - contractAddresses?: string[] - includeMetadata?: boolean - omitPrices?: boolean - tokenIds?: string[] - contractStatus?: ContractVerificationStatus - page?: TokenBalancesPage -}): Promise +export type OMSWalletSessionAuth = OMSWalletEmailSessionAuth | OMSWalletOidcSessionAuth; ``` -Fetches native and token balances for a wallet. Pass `networks` to query explicit SDK network objects. If `networks` is omitted, the request defaults to `networkType: 'MAINNETS'`. The default request returns page `0` with up to `40` entries. `includeMetadata` defaults to `true`; token display data is returned on `contractInfo` and `tokenMetadata`, and ERC-20 decimals are available as `contractInfo.decimals`. - -**Parameters** - -| Name | Type | Description | -|---|---|---| -| `walletAddress` | `string` | The wallet address whose balances to fetch. Use `oms.wallet.walletAddress` after checking it is defined. | -| `networks` | `Network[]` | Optional explicit networks to query. Use exported registry values such as `Networks.polygon`. | -| `networkType` | `'MAINNETS' \| 'TESTNETS' \| 'ALL'` | Optional gateway network group when `networks` is omitted. Defaults to `'MAINNETS'`. | -| `contractAddresses` | `string[]` | Optional token contract filter. Omit to query balances across contracts. | -| `includeMetadata` | `boolean` | Optional metadata flag. Defaults to `true`. | -| `omitPrices` | `boolean` | Optional price exclusion flag. | -| `tokenIds` | `string[]` | Optional token ID filter. | -| `contractStatus` | `ContractVerificationStatus` | Optional contract verification filter. | -| `page` | `TokenBalancesPage` | Optional pagination request. Defaults to `{ page: 0, pageSize: 40 }`. | - -**Returns** `Promise` — see [BalancesResult](#balancesresult). - -**Example** +### `OMSWalletSessionState` ```typescript -const { walletAddress } = oms.wallet -if (!walletAddress) throw new Error('No active wallet session') - -const result = await oms.indexer.getBalances({ - networks: [Networks.polygon], - walletAddress, - includeMetadata: true, -}) - -for (const b of result.nativeBalances) { - console.log(b.symbol, b.balance) -} - -for (const b of result.balances) { - console.log(b.contractAddress, b.balance, b.tokenId) +export interface OMSWalletSessionState { + readonly walletAddress: Address | undefined; + readonly expiresAt: string | undefined; + readonly auth: OMSWalletSessionAuth | undefined; } ``` ---- - -### getTransactionHistory +### `OMSWalletSessionExpiredEvent` ```typescript -getTransactionHistory(params: { - walletAddress: string - networks?: Network[] - networkType?: 'MAINNETS' | 'TESTNETS' | 'ALL' - contractAddresses?: string[] - transactionHashes?: string[] - metaTransactionIds?: string[] - fromBlock?: number - toBlock?: number - tokenId?: string - includeMetadata?: boolean - omitPrices?: boolean - metadataOptions?: MetadataOptions - page?: TokenBalancesPage -}): Promise +export interface OMSWalletSessionExpiredEvent { + readonly session: OMSWalletSessionState; + readonly expiredAt: string; +} ``` -Fetches mined transaction history for a wallet. Pass `networks` to query explicit SDK network objects. If `networks` is omitted, the request defaults to `networkType: 'MAINNETS'`. `includeMetadata` defaults to `true`. Use `metadataOptions` to tune returned token and contract metadata. - -**Parameters** - -| Name | Type | Description | -|---|---|---| -| `walletAddress` | `string` | The wallet address whose transaction history to fetch. Use `oms.wallet.walletAddress` after checking it is defined. | -| `networks` | `Network[]` | Optional explicit networks to query. Use exported registry values such as `Networks.polygon`. | -| `networkType` | `'MAINNETS' \| 'TESTNETS' \| 'ALL'` | Optional network group when `networks` is omitted. Defaults to `'MAINNETS'`. | -| `contractAddresses` | `string[]` | Optional token contract filter. | -| `transactionHashes` | `string[]` | Optional transaction hash filter. | -| `metaTransactionIds` | `string[]` | Optional meta-transaction ID filter. | -| `fromBlock` | `number` | Optional starting block number. | -| `toBlock` | `number` | Optional ending block number. | -| `tokenId` | `string` | Optional token ID filter. | -| `includeMetadata` | `boolean` | Optional metadata flag. Defaults to `true`. | -| `omitPrices` | `boolean` | Optional price exclusion flag. | -| `metadataOptions` | `MetadataOptions` | Optional metadata enrichment filters. See [MetadataOptions](#metadataoptions). | -| `page` | `TokenBalancesPage` | Optional pagination request. | - -**Returns** `Promise` — see [TransactionHistoryResult](#transactionhistoryresult). - ---- +### `OMSWalletSessionExpiredListener` -## Errors +```typescript +export type OMSWalletSessionExpiredListener = (event: OMSWalletSessionExpiredEvent) => void | Promise; +``` -Public methods throw `OmsSdkError` subclasses for SDK-level failures. +### `GetIdTokenParams` ```typescript -class OmsSdkError extends Error { - code: OmsSdkErrorCode - operation?: string - status?: number - txnId?: string - retryable?: boolean - upstreamError?: OmsUpstreamError - cause?: unknown +export interface GetIdTokenParams { + ttlSeconds?: number; + customClaims?: Record; } ``` +### `WalletCredential` + ```typescript -interface OmsUpstreamError { - service: 'waas' | 'indexer' - name?: string - code?: number | string - message?: string - status?: number +export interface WalletCredential { + credentialId: string; + expiresAt: string; + isCaller: boolean; } ``` +### `AccessGrant` + ```typescript -type OmsSdkErrorCode = - | 'OMS_HTTP_ERROR' - | 'OMS_INVALID_RESPONSE' - | 'OMS_REQUEST_FAILED' - | 'OMS_AUTH_COMMITMENT_CONSUMED' - | 'OMS_SESSION_MISSING' - | 'OMS_SESSION_EXPIRED' - | 'OMS_WALLET_SELECTION_STALE' - | 'OMS_WALLET_SELECTION_UNAVAILABLE' - | 'OMS_WALLET_SELECTION_IN_FLIGHT' - | 'OMS_TRANSACTION_EXECUTION_UNCONFIRMED' - | 'OMS_TRANSACTION_STATUS_LOOKUP_FAILED' - | 'OMS_VALIDATION_ERROR' +export type AccessGrant = WalletCredential; ``` -`OMS_AUTH_COMMITMENT_CONSUMED` means the OTP/OIDC auth commitment has already been used. Restart the auth flow before retrying. - -`OMS_TRANSACTION_EXECUTION_UNCONFIRMED` means transaction preparation succeeded, but the execute request failed before the SDK could confirm whether the transaction was submitted. The error includes `txnId` when available; do not blindly resend the same write solely because the upstream failure looked temporary. - -`OMS_TRANSACTION_STATUS_LOOKUP_FAILED` means the transaction was submitted, but post-submit status polling failed. The error includes `txnId` and is retryable by checking status again with `getTransactionStatus`. - -`upstreamError` is normalized diagnostic detail from a remote OMS service response or transport failure. Use the SDK-level `code` for application branching; use `upstreamError` for logging and service-specific troubleshooting. - -`retryable` describes the failed SDK operation, not the whole user intent. For example, a retryable transaction status lookup failure means retry `getTransactionStatus`; it does not mean blindly resend the original transaction write. - -| Class | Typical use | -|---|---| -| `OmsSessionError` | Missing, expired, or stale wallet session. | -| `OmsRequestError` | Network, fetch, or non-2xx HTTP failures. | -| `OmsResponseError` | Invalid JSON or malformed API responses. | -| `OmsTransactionError` | Transaction execution could not be confirmed or submitted transaction status polling failed; includes `txnId` when available. | -| `OmsWalletSelectionError` | Manual wallet selection is stale, invalid, or already processing an action. | -| `OmsValidationError` | SDK-side validation failures before a request is sent. | - -Use `isOmsSdkError(err)` or `err instanceof OmsSdkError` to branch on structured error fields. - ---- - -## Types - -### Network +### `ListAccessParams` ```typescript -interface Network { - readonly id: number - readonly name: string - readonly nativeTokenSymbol: string - readonly explorerUrl: string - readonly displayName: string +export interface ListAccessParams { + pageSize?: number; } ``` -A supported OMS network entry. The SDK exports `Networks`, `supportedNetworks`, `findNetworkById(id)`, and `findNetworkByName(name)`. -`name` is the registry/routing slug for indexer URLs, while `displayName` is the user-facing label. +### `AccessGrantPage` ```typescript -findNetworkById(id: number): Network | undefined -findNetworkByName(name: string): Network | undefined +export interface AccessGrantPage { + grants: AccessGrant[]; +} ``` -| Key | id | name | displayName | nativeTokenSymbol | explorerUrl | -|---|---:|---|---|---|---| -| `Networks.mainnet` | 1 | `mainnet` | `Ethereum` | `ETH` | `https://etherscan.io` | -| `Networks.sepolia` | 11155111 | `sepolia` | `Sepolia` | `ETH` | `https://sepolia.etherscan.io` | -| `Networks.polygon` | 137 | `polygon` | `Polygon` | `POL` | `https://polygonscan.com` | -| `Networks.amoy` | 80002 | `amoy` | `Polygon Amoy` | `POL` | `https://amoy.polygonscan.com` | -| `Networks.arbitrum` | 42161 | `arbitrum` | `Arbitrum` | `ETH` | `https://arbiscan.io` | -| `Networks.arbitrumSepolia` | 421614 | `arbitrum-sepolia` | `Arbitrum Sepolia` | `ETH` | `https://sepolia.arbiscan.io` | -| `Networks.optimism` | 10 | `optimism` | `Optimism` | `ETH` | `https://optimistic.etherscan.io` | -| `Networks.optimismSepolia` | 11155420 | `optimism-sepolia` | `Optimism Sepolia` | `ETH` | `https://sepolia-optimism.etherscan.io` | -| `Networks.base` | 8453 | `base` | `Base` | `ETH` | `https://basescan.org` | -| `Networks.baseSepolia` | 84532 | `base-sepolia` | `Base Sepolia` | `ETH` | `https://sepolia.basescan.org` | -| `Networks.bsc` | 56 | `bsc` | `BSC` | `BNB` | `https://bscscan.com` | -| `Networks.bscTestnet` | 97 | `bsc-testnet` | `BSC Testnet` | `BNB` | `https://testnet.bscscan.com` | -| `Networks.arbitrumNova` | 42170 | `arbitrum-nova` | `Arbitrum Nova` | `ETH` | `https://nova.arbiscan.io` | -| `Networks.avalanche` | 43114 | `avalanche` | `Avalanche` | `AVAX` | `https://subnets.avax.network/c-chain` | -| `Networks.avalancheTestnet` | 43113 | `avalanche-testnet` | `Avalanche Testnet` | `AVAX` | `https://subnets-test.avax.network/c-chain` | -| `Networks.katana` | 747474 | `katana` | `Katana` | `ETH` | `https://katanascan.com` | - -### OmsAuthConfig +### `StorageManager` ```typescript -interface OmsAuthConfig { - oidcProviders?: Record +export interface StorageManager { + get(key: string): string | null; + set(key: string, value: string): void; + delete(key: string): void; } ``` -| Field | Type | Description | -|---|---|---| -| `oidcProviders` | `Record` | OIDC provider configurations addressable by provider key. | - -When `auth` is omitted, the SDK configures the built-in `google` and `apple` providers. Passing `auth` replaces the configured provider set. +### `LocalStorageManager` -Use `defineOmsAuthConfig` to preserve typed custom OIDC provider keys: +Browser implementation backed by localStorage. +For Node.js or custom runtimes, supply your own StorageManager to OMSWallet. ```typescript -const auth = defineOmsAuthConfig({ - oidcProviders: { - custom: customOidcProvider, - }, -}) - -const oms = new OMSClient({ - publishableKey: 'your-publishable-key', - auth, -}) +export declare class LocalStorageManager implements StorageManager { + static isAvailable(): boolean; + get(key: string): string | null; + set(key: string, value: string): void; + delete(key: string): void; +} ``` ---- - -### OidcProviderConfig +### `SessionStorageManager` ```typescript -type OidcProviderConfig = { - clientId: string - issuer: string - authorizationUrl: string - scopes?: string[] - relayRedirectUri?: string - authorizeParams?: Record - authMode?: AuthMode.AuthCode | AuthMode.AuthCodePKCE +export declare class SessionStorageManager implements StorageManager { + static isAvailable(): boolean; + get(key: string): string | null; + set(key: string, value: string): void; + delete(key: string): void; } ``` -Provider configs are the source of truth for authorization scopes. If `scopes` is omitted or empty, the SDK does not send a `scope` authorization parameter. `authMode` defaults to `AuthMode.AuthCodePKCE`. - -Google can be configured with the `googleOidcProvider` helper. The default Google provider uses the SDK default client ID, the SDK relay redirect URI, `openid email profile` scopes, PKCE auth-code mode, and Google authorization parameters `access_type=offline` and `prompt=consent`: +### `MemoryStorageManager` ```typescript -// Uses the SDK default Google client id and relay redirect URI. -googleOidcProvider() - -// Override defaults when needed. -googleOidcProvider({ - clientId: 'your-google-client-id', - relayRedirectUri: 'http://localhost:8090/callback', -}) +export declare class MemoryStorageManager implements StorageManager { + get(key: string): string | null; + set(key: string, value: string): void; + delete(key: string): void; +} ``` -Apple can be configured with the `appleOidcProvider` helper. The default Apple provider uses `openid email` scopes, `response_mode=form_post`, and PKCE auth-code mode: +### `createDefaultStorage` ```typescript -// Uses the SDK default Apple Services ID and relay redirect URI. -appleOidcProvider() - -// Override defaults when needed. -appleOidcProvider({ - clientId: 'your-apple-services-id', - relayRedirectUri: 'https://app.example/auth/callback', -}) +export declare function createDefaultStorage(): StorageManager; ``` ---- - -### OIDC Provider Helpers +### `CredentialSigningAlgorithm` ```typescript -type OidcProviderName = - keyof NonNullable['oidcProviders']> & string -type OidcProviderInput = OidcProviderName | OidcProviderConfig - -interface GoogleOidcProviderParams { - clientId?: string - relayRedirectUri?: string - scopes?: string[] - authorizeParams?: Record - authMode?: AuthMode.AuthCode | AuthMode.AuthCodePKCE -} - -interface AppleOidcProviderParams { - clientId?: string - relayRedirectUri?: string - scopes?: string[] - authorizeParams?: Record - authMode?: AuthMode.AuthCode | AuthMode.AuthCodePKCE -} - -const defaultOmsAuthConfig: OmsAuthConfig +export type CredentialSigningAlgorithm = "ecdsa-p256-sha256" | "ecdsa-p256k-eip191"; ``` -`OidcProviderName` is narrowed from the configured `auth.oidcProviders` keys when `OMSClient` is constructed with `defineOmsAuthConfig`. `googleOidcProvider(params)` and `appleOidcProvider(params)` return `OidcProviderConfig` values. `defaultOmsAuthConfig` contains the SDK's built-in Google and Apple provider configuration. - ---- - -### Auth Method Types +### `CredentialSigner` ```typescript -interface CompleteEmailAuthParams { - code: string - walletType?: WalletType - walletSelection?: WalletSelectionBehavior - sessionLifetimeSeconds?: number -} - -interface CompleteEmailAuthResult { - walletAddress: Address - wallet: OmsWallet - wallets: OmsWallet[] - credential: WalletCredential -} - -interface StartOidcRedirectAuthParams { - provider: OidcProviderInput - redirectUri?: string - walletType?: WalletType - walletSelection?: WalletSelectionBehavior - sessionLifetimeSeconds?: number - relayRedirectUri?: string - authorizeParams?: Record - loginHint?: string -} - -interface StartOidcRedirectAuthResult { - url: string - state: string - challenge: string +export interface CredentialSigner { + readonly signingAlgorithm: CredentialSigningAlgorithm; + credentialId(): Promise; + nextNonce(): Promise; + sign(preimage: string): Promise; + hasCredential?(): Promise; + clear?(): Promise; } +``` -interface CompleteOidcRedirectAuthParams { - callbackUrl?: string - cleanUrl?: boolean - replaceUrl?: (url: string) => void - walletSelection?: WalletSelectionBehavior - sessionLifetimeSeconds?: number -} +### `WebCryptoP256CredentialSigner` -interface CompleteOidcRedirectAuthResult { - walletAddress: Address - wallet: OmsWallet - wallets: OmsWallet[] - credential: WalletCredential -} - -interface SignInWithOidcRedirectParams { - provider: OidcProviderInput - redirectUri?: string - walletType?: WalletType - walletSelection?: WalletSelectionBehavior - relayRedirectUri?: string - authorizeParams?: Record - loginHint?: string - sessionLifetimeSeconds?: number - currentUrl?: string - assignUrl?: (url: string) => void +```typescript +export declare class WebCryptoP256CredentialSigner implements CredentialSigner { + readonly signingAlgorithm: "ecdsa-p256-sha256"; + constructor(id?: string); + credentialId(): Promise; + nextNonce(): Promise; + sign(preimage: string): Promise; + hasCredential(): Promise; + clear(): Promise; } ``` -Exported parameter and result interfaces for the email OTP and OIDC redirect methods documented above. - ---- - -### StorageManager +### `EthereumPrivateKeyCredentialSigner` ```typescript -interface StorageManager { - get(key: string): string | null - set(key: string, value: string): void - delete(key: string): void +export declare class EthereumPrivateKeyCredentialSigner implements CredentialSigner { + readonly signingAlgorithm: "ecdsa-p256k-eip191"; + constructor(privateKey: Uint8Array); + credentialId(): Promise; + nextNonce(): Promise; + sign(preimage: string): Promise; } ``` -Interface for wallet metadata storage. Implement this to use a custom backend. The SDK defaults to `LocalStorageManager` when browser `localStorage` is available and `MemoryStorageManager` otherwise. - ---- +## Transactions and signing -### Storage Helpers +### `OMSWalletClient.signMessage` ```typescript -class LocalStorageManager implements StorageManager -class SessionStorageManager implements StorageManager -class MemoryStorageManager implements StorageManager - -function createDefaultStorage(): StorageManager +signMessage(params: SignMessageParams): Promise; ``` -`createDefaultStorage()` returns `LocalStorageManager` when browser `localStorage` is available, otherwise `MemoryStorageManager`. OIDC redirect state defaults to `SessionStorageManager` when browser `sessionStorage` is available. - ---- - -### CredentialSigner +### `OMSWalletClient.signTypedData` ```typescript -type CredentialSigningAlgorithm = 'ecdsa-p256k-eip191' | 'ecdsa-p256-sha256' - -interface CredentialSigner { - readonly signingAlgorithm: CredentialSigningAlgorithm - credentialId(): Promise - nextNonce(): Promise - sign(preimage: string): Promise - hasCredential?(): Promise - clear?(): Promise -} +signTypedData(params: SignTypedDataParams): Promise; ``` -Interface for request credential signing. The default implementation is `WebCryptoP256CredentialSigner`, which uses `ecdsa-p256-sha256` and a non-extractable WebCrypto private key. - ---- - -### Credential Signing Helpers +### `OMSWalletClient.isValidMessageSignature` ```typescript -class WebCryptoP256CredentialSigner implements CredentialSigner { - constructor(id?: string) -} - -class EthereumPrivateKeyCredentialSigner implements CredentialSigner { - constructor(privateKey: Uint8Array) -} +isValidMessageSignature(params: IsValidMessageSignatureParams): Promise; ``` -`WebCryptoP256CredentialSigner` is the browser default. `EthereumPrivateKeyCredentialSigner` signs credential requests with an EVM private key and is useful for Node.js or server-side usage where the caller provides key material directly. +### `OMSWalletClient.isValidTypedDataSignature` ---- +```typescript +isValidTypedDataSignature(params: IsValidTypedDataSignatureParams): Promise; +``` -### Session Listener Types +### `OMSWalletClient.sendTransaction` ```typescript -type OMSClientSessionLoginType = 'email' | 'google-auth' | 'oidc' - -interface OMSClientSessionState { - walletAddress: Address | undefined - expiresAt: string | undefined - loginType: OMSClientSessionLoginType | undefined - sessionEmail: string | undefined -} +sendTransaction(params: SendNativeTransactionParams): Promise; +sendTransaction(params: SendDataTransactionParams): Promise; +sendTransaction | undefined = ContractFunctionName>(params: SendContractTransactionParams): Promise; +sendTransaction(params: SendTransactionParams): Promise; +``` -interface OMSClientSessionExpiredEvent { - session: OMSClientSessionState - expiredAt: string -} +### `OMSWalletClient.callContract` -type OMSClientSessionExpiredListener = ( - event: OMSClientSessionExpiredEvent -) => void | Promise +```typescript +callContract(params: { + network: Network; + contractAddress: Address; + method: string; + args?: Array; + mode?: TransactionMode; + selectFeeOption?: FeeOptionSelector; + waitForStatus?: boolean; + statusPolling?: TransactionStatusPollingOptions; +}): Promise; ``` -Session state and listener types used by [`session`](#session) and [`onSessionExpired`](#onsessionexpired). +### `OMSWalletClient.getTransactionStatus` ---- +```typescript +getTransactionStatus(params: { + txnId: string; +}): Promise; +``` -### Signing and Validation Method Types +### `SignMessageParams` ```typescript -interface SignMessageParams { - network: Network - message: string +export interface SignMessageParams { + network: Network; + message: string; } +``` -interface SignTypedDataParams { - network: Network - typedData: any -} +### `SignTypedDataParams` -interface GetIdTokenParams { - ttlSeconds?: number - customClaims?: Record +```typescript +export interface SignTypedDataParams { + network: Network; + typedData: unknown; } +``` -interface IsValidMessageSignatureParams { - network?: Network - walletAddress?: Address - walletId?: string - message: string - signature: string -} +### `IsValidMessageSignatureParams` -interface IsValidTypedDataSignatureParams { - network?: Network - walletAddress?: Address - walletId?: string - typedData: any - signature: string +```typescript +export interface IsValidMessageSignatureParams { + network?: Network; + walletAddress?: Address; + walletId?: string; + message: string; + signature: string; } ``` -Exported parameter interfaces for signing, ID token, and signature validation methods. - ---- - -### OmsWallet +### `IsValidTypedDataSignatureParams` ```typescript -interface OmsWallet { - id: string - type: WalletType - address: Address - reference?: string +export interface IsValidTypedDataSignatureParams { + network?: Network; + walletAddress?: Address; + walletId?: string; + typedData: unknown; + signature: string; } ``` -Wallet metadata returned by auth and wallet listing APIs. - ---- - -### PendingWalletSelection +### `AbiArg` ```typescript -interface PendingWalletSelection { - walletType: WalletType - wallets: OmsWallet[] - credential: WalletCredential - - selectWallet(params: { walletId: string }): Promise - createAndSelectWallet(params?: { reference?: string }): Promise +export interface AbiArg { + type: string; + value: unknown; } ``` -Returned by manual email or OIDC auth completion. The selection is bound to the verified auth flow and signer that created it. It can be used once to select one of the returned `wallets` or to create and select a new wallet of `walletType`. - ---- - -### WalletSelectionBehavior +### `SendTransactionBase` ```typescript -type WalletSelectionBehavior = 'automatic' | 'manual' +export type SendTransactionBase = { + network: Network; + to: Address; + value?: bigint; + mode?: TransactionMode; + selectFeeOption?: FeeOptionSelector; + waitForStatus?: boolean; + statusPolling?: TransactionStatusPollingOptions; +}; ``` -Controls whether auth completion immediately activates a wallet or returns a [`PendingWalletSelection`](#pendingwalletselection). - ---- - -### WalletCredential +### `SendNativeTransactionParams` ```typescript -interface WalletCredential { - credentialId: string - expiresAt: string - isCaller: boolean -} +export type SendNativeTransactionParams = SendTransactionBase & { + value: bigint; + data?: never; + abi?: never; +}; ``` -| Field | Type | Description | -|---|---|---| -| `credentialId` | `string` | Unique identifier. Pass to `revokeAccess` to remove this credential. | -| `expiresAt` | `string` | ISO 8601 timestamp for credential expiry. | -| `isCaller` | `boolean` | `true` if this credential belongs to the current active session. | - -`AccessGrant` has the same shape and represents a credential with access to the active wallet. - ---- - -### AccessGrant +### `SendDataTransactionParams` ```typescript -type AccessGrant = WalletCredential +export type SendDataTransactionParams = SendTransactionBase & { + data: Hex; + abi?: never; +}; ``` ---- - -### ListAccessParams +### `SendContractTransactionParams` ```typescript -interface ListAccessParams { - pageSize?: number -} +export type SendContractTransactionParams | undefined = ContractFunctionName> = SendTransactionBase & EncodeFunctionDataParameters & { + data?: never; +}; ``` -| Field | Type | Description | -|---|---|---| -| `pageSize` | `number` | Requested page size. The service applies its own default and maximum. | - ---- - -### AccessGrantPage +### `SendTransactionParams` ```typescript -interface AccessGrantPage { - grants: AccessGrant[] -} +export type SendTransactionParams = SendNativeTransactionParams | SendDataTransactionParams | SendContractTransactionParams; ``` -| Field | Type | Description | -|---|---|---| -| `grants` | `AccessGrant[]` | Credentials yielded for this page. | - ---- - -### WalletActivationResult +### `SendTransactionResponse` ```typescript -interface WalletActivationResult { - walletAddress: Address - wallet: OmsWallet -} +export type SendTransactionResponse = { + txnId: string; + status: TransactionStatus; + txnHash?: string; + statusResolution: "not-requested" | "resolved" | "timed-out"; +}; ``` -Returned when an existing wallet is selected or a new wallet is created and activated. - ---- - -### Native Transaction Parameters +### `TransactionMode` ```typescript -{ - network: Network - to: Address - value: bigint - mode?: TransactionMode - selectFeeOption?: FeeOptionSelector - waitForStatus?: boolean - statusPolling?: TransactionStatusPollingOptions -} +export declare const TransactionMode: Readonly<{ + readonly Native: "native"; + readonly Relayer: "relayer"; +}>; +export type TransactionMode = (typeof TransactionMode)[keyof typeof TransactionMode]; ``` -Used when sending a native token transfer. `value` is required and `data`/`abi` must not be set. - ---- - -### Raw Data Transaction Parameters +### `TransactionStatus` ```typescript -{ - network: Network - to: Address - data: Hex - value?: bigint - mode?: TransactionMode - selectFeeOption?: FeeOptionSelector - waitForStatus?: boolean - statusPolling?: TransactionStatusPollingOptions -} +export declare const TransactionStatus: Readonly<{ + readonly Quoted: "quoted"; + readonly Pending: "pending"; + readonly Executed: "executed"; + readonly Failed: "failed"; + readonly Unknown: "unknown"; +}>; +export type TransactionStatus = (typeof TransactionStatus)[keyof typeof TransactionStatus]; ``` -Used when sending a transaction with raw calldata. `abi` must not be set. - ---- - -### ABI-Encoded Transaction Parameters +### `TransactionStatusResponse` ```typescript -{ - network: Network - to: Address - abi: Abi | readonly unknown[] - functionName: string - args?: unknown[] - value?: bigint - mode?: TransactionMode - selectFeeOption?: FeeOptionSelector - waitForStatus?: boolean - statusPolling?: TransactionStatusPollingOptions +export interface TransactionStatusResponse { + status: TransactionStatus; + txnHash?: string; } ``` -Used for ABI-encoded contract calls. `abi` and `functionName` are required; `args` types are inferred from const ABIs. `data` must not be set. Calldata is encoded automatically using viem's `encodeFunctionData`. - ---- - -### SendTransactionResponse +### `TransactionStatusPollingOptions` ```typescript -type SendTransactionResponse = { - txnId: string - status: TransactionStatus - txnHash?: string -} +export type TransactionStatusPollingOptions = { + timeoutMs?: number; + intervalMs?: number; + fastIntervalMs?: number; + fastPollCount?: number; +}; ``` -`txnHash` is present once the transaction is published. If polling times out while the transaction is still pending, use `txnId` to check status later. - ---- - -### TransactionStatusResponse +### `FeeOption` ```typescript -interface TransactionStatusResponse { - status: TransactionStatus - txnHash?: string +export interface FeeOption { + token: { + network: string; + name: string; + symbol: string; + type: string; + decimals?: number; + logoURL?: string; + contractAddress?: string; + tokenID?: string; + }; + value: string; + displayValue: string; } ``` -Returned by [`getTransactionStatus`](#gettransactionstatus). - ---- - -### TransactionStatusPollingOptions +### `FeeOptionSelection` ```typescript -type TransactionStatusPollingOptions = { - timeoutMs?: number - intervalMs?: number - fastIntervalMs?: number - fastPollCount?: number +export interface FeeOptionSelection { + token: string; } ``` -Controls how `sendTransaction` polls transaction status after execute when `waitForStatus` is not `false`. +### `FeeOptionWithBalance` ---- +```typescript +export type FeeOptionWithBalance = { + feeOption: FeeOption; + selection: FeeOptionSelection; + balance?: TokenBalance; + available?: string; + availableRaw?: string; + decimals?: number; +}; +``` -### TransactionMode +### `FeeOptionSelector` ```typescript -enum TransactionMode { - Native = 'native', - Relayer = 'relayer' +export interface FeeOptionSelector { + (feeOptions: FeeOptionWithBalance[]): FeeOptionSelection | undefined | Promise; +} +export declare namespace FeeOptionSelector { + const firstAvailable: FeeOptionSelector; } ``` -Controls how the SDK prepares a wallet transaction. Transaction methods default to `TransactionMode.Relayer`. +## Indexer ---- - -### TransactionStatus +### `OMSWalletIndexerClient` ```typescript -enum TransactionStatus { - Quoted = 'quoted', - Pending = 'pending', - Executed = 'executed', - Failed = 'failed' +export interface OMSWalletIndexerClient { + getBalances(params: GetBalancesParams): Promise; + getTransactionHistory(params: GetTransactionHistoryParams): Promise; } ``` -Returned in transaction execution and status responses. - ---- - -### FeeOptionSelector +### `GetBalancesParams` ```typescript -type FeeOptionSelector = ( - feeOptions: FeeOptionWithBalance[] -) => FeeOptionSelection | undefined | Promise - -const FeeOptionSelector: { - firstAvailable: FeeOptionSelector +export interface GetBalancesParams { + walletAddress: string; + networks?: Network[]; + networkType?: IndexerNetworkType; + contractAddresses?: string[]; + includeMetadata?: boolean; + omitPrices?: boolean; + tokenIds?: string[]; + contractStatus?: ContractVerificationStatus; + page?: TokenBalancesPageRequest; } ``` -When no selector is provided, the SDK uses the first required fee option, or no -fee option for sponsored transactions. `FeeOptionSelector.firstAvailable` uses -enriched balances to skip underfunded fee options and selects the first option -the wallet can pay. For custom selectors, return `option.selection` to select -that fee option. - ---- - -### FeeOption +### `BalancesResult` ```typescript -interface FeeOption { - token: { - network: string - name: string - symbol: string - type: string - decimals?: number - logoURL?: string - contractAddress?: string - tokenID?: string - } - value: string - displayValue: string +export interface BalancesResult { + status: number; + page?: TokenBalancesPage; + nativeBalances: NativeTokenBalance[]; + balances: ContractTokenBalance[]; } ``` -A fee token option returned during transaction preparation. `value` is the token amount in base units. - ---- - -### FeeOptionSelection +### `GetTransactionHistoryParams` ```typescript -interface FeeOptionSelection { - token: string +export interface GetTransactionHistoryParams { + walletAddress: string; + networks?: Network[]; + networkType?: IndexerNetworkType; + contractAddresses?: string[]; + transactionHashes?: string[]; + metaTransactionIds?: string[]; + fromBlock?: number; + toBlock?: number; + tokenId?: string; + includeMetadata?: boolean; + omitPrices?: boolean; + metadataOptions?: MetadataOptions; + page?: TokenBalancesPageRequest; } ``` -The selector payload for a fee option. In custom selectors, return the `selection` field from `FeeOptionWithBalance`. - ---- - -### FeeOptionWithBalance +### `TransactionHistoryResult` ```typescript -type FeeOptionWithBalance = { - feeOption: FeeOption - selection: FeeOptionSelection - balance?: TokenBalance - available?: string - availableRaw?: string - decimals?: number +export interface TransactionHistoryResult { + status: number; + page?: TokenBalancesPage; + transactions: Transaction[]; } ``` -Fee option plus the active wallet's indexer balance for that token, when the SDK can load it. - ---- - -### GetBalancesParams +### `IndexerNetworkType` ```typescript -interface GetBalancesParams { - walletAddress: string - networks?: Network[] - networkType?: IndexerNetworkType - contractAddresses?: string[] - includeMetadata?: boolean - omitPrices?: boolean - tokenIds?: string[] - contractStatus?: ContractVerificationStatus - page?: TokenBalancesPage -} +export type IndexerNetworkType = "MAINNETS" | "TESTNETS" | "ALL"; ``` -Parameters for [`getBalances`](#getbalances). +### `ContractVerificationStatus` ---- +```typescript +export type ContractVerificationStatus = "VERIFIED" | "UNVERIFIED" | "ALL"; +``` -### GetTransactionHistoryParams +### `MetadataOptions` ```typescript -interface GetTransactionHistoryParams { - walletAddress: string - networks?: Network[] - networkType?: IndexerNetworkType - contractAddresses?: string[] - transactionHashes?: string[] - metaTransactionIds?: string[] - fromBlock?: number - toBlock?: number - tokenId?: string - includeMetadata?: boolean - omitPrices?: boolean - metadataOptions?: MetadataOptions - page?: TokenBalancesPage +export interface MetadataOptions { + verifiedOnly?: boolean; + unverifiedOnly?: boolean; + includeContracts?: string[]; } ``` -Parameters for [`getTransactionHistory`](#gettransactionhistory). - ---- - -### IndexerNetworkType +### `SortBy` ```typescript -type IndexerNetworkType = 'MAINNETS' | 'TESTNETS' | 'ALL' +export interface SortBy { + column: string; + order: "DESC" | "ASC"; +} ``` -Network group used when explicit `networks` are not provided. - ---- - -### ContractVerificationStatus +### `TokenBalancesPageRequest` ```typescript -type ContractVerificationStatus = 'VERIFIED' | 'UNVERIFIED' | 'ALL' +export interface TokenBalancesPageRequest { + page?: number; + column?: string; + before?: unknown; + after?: unknown; + sort?: SortBy[]; + pageSize?: number; +} ``` -Optional contract verification filter for balance queries. - ---- - -### MetadataOptions +### `TokenBalancesPage` ```typescript -interface MetadataOptions { - verifiedOnly?: boolean - unverifiedOnly?: boolean - includeContracts?: string[] +export interface TokenBalancesPage { + page: number; + column?: string; + before?: unknown; + after?: unknown; + sort?: SortBy[]; + pageSize: number; + more: boolean; } ``` -Options for transaction metadata enrichment. Use `contractStatus` on [`getBalances`](#getbalances) to filter balance queries by contract verification status; use `metadataOptions` on [`getTransactionHistory`](#gettransactionhistory) to tune metadata returned with transaction history. - -| Field | Type | Description | -|---|---|---| -| `verifiedOnly` | `boolean` | Request metadata for verified contracts only. | -| `unverifiedOnly` | `boolean` | Request metadata for unverified contracts only. | -| `includeContracts` | `string[]` | Limit metadata enrichment to specific contract addresses. | +### `TokenBalance` ---- +```typescript +export type TokenBalance = NativeTokenBalance | ContractTokenBalance; +``` -### SortBy +### `NativeTokenBalance` ```typescript -interface SortBy { - column: string - order: 'DESC' | 'ASC' +export interface NativeTokenBalance { + accountAddress: string; + balance: string; + chainId: number; + balanceUSD?: string; + priceUSD?: string; + priceUpdatedAt?: string; + contractType: "NATIVE"; + name: string; + symbol: string; + contractAddress?: undefined; + tokenId?: undefined; } ``` -Sort descriptor used in indexer pagination requests. - ---- - -### BalancesResult +### `ContractTokenBalance` ```typescript -interface BalancesResult { - status: number - page?: TokenBalancesPage - nativeBalances: TokenBalance[] - balances: TokenBalance[] +export interface ContractTokenBalance { + contractType: string; + accountAddress: string; + balance: string; + chainId: number; + balanceUSD?: string; + priceUSD?: string; + priceUpdatedAt?: string; + contractAddress: string; + tokenId: string; + blockHash: string; + blockNumber: number; + uniqueCollectibles?: string; + isSummary?: boolean; + contractInfo?: TokenContractInfo; + tokenMetadata?: TokenMetadata; } ``` -| Field | Type | Description | -|---|---|---| -| `status` | `number` | Response status code. | -| `page` | `TokenBalancesPage` | Pagination metadata, if present. | -| `nativeBalances` | `TokenBalance[]` | Native token balances for the requested address. | -| `balances` | `TokenBalance[]` | Array of token balance entries for the requested address. | - ---- - -### TransactionHistoryResult +### `TokenContractInfo` ```typescript -interface TransactionHistoryResult { - status: number - page?: TokenBalancesPage - transactions: Transaction[] +export interface TokenContractInfo { + chainId: number; + address: string; + source: string; + name: string; + type: string; + symbol: string; + decimals?: number; + logoURI?: string; + deployed: boolean; + bytecodeHash: string; + extensions: Record; + updatedAt: string; + queuedAt?: string; + status: string; } ``` -| Field | Type | Description | -|---|---|---| -| `status` | `number` | Response status code. | -| `page` | `TokenBalancesPage` | Pagination metadata, if present. | -| `transactions` | `Transaction[]` | Flattened transaction entries across the requested networks. | - ---- - -### Transaction +### `TokenMetadata` ```typescript -interface Transaction { - txnHash: string - blockNumber: number - blockHash: string - chainId: number - metaTxnId?: string - transfers?: TransactionTransfer[] - timestamp: string +export interface TokenMetadata { + chainId?: number; + contractAddress?: string; + tokenId: string; + source: string; + name: string; + description?: string; + image?: string; + video?: string; + audio?: string; + properties?: Record; + attributes: Record[]; + imageData?: string; + externalUrl?: string; + backgroundColor?: string; + animationUrl?: string; + decimals?: number; + updatedAt?: string; + assets?: TokenMetadataAsset[]; + status: string; + queuedAt?: string; + lastFetched?: string; } ``` -Indexer transaction entry returned by [`getTransactionHistory`](#gettransactionhistory). - ---- - -### TransactionTransfer +### `TokenMetadataAsset` ```typescript -interface TransactionTransfer { - transferType?: string - contractAddress?: string - contractType?: string - from?: string - to?: string - tokenIds?: string[] - amounts?: string[] - logIndex?: number - amountsUSD?: string[] - pricesUSD?: string[] - contractInfo?: TokenContractInfo - tokenMetadata?: Record +export interface TokenMetadataAsset { + id?: number; + collectionId?: number; + tokenId?: string; + url?: string; + metadataField?: string; + name?: string; + filesize?: number; + mimeType?: string; + width?: number; + height?: number; + updatedAt?: string; +} +``` + +### `Transaction` + +```typescript +export interface Transaction { + txnHash: string; + blockNumber: number; + blockHash: string; + chainId: number; + metaTxnId?: string; + transfers: TransactionTransfer[]; + timestamp: string; +} +``` + +### `TransactionTransfer` + +```typescript +export interface TransactionTransfer { + transferType: string; + contractAddress: string; + contractType: string; + from: string; + to: string; + tokenIds?: string[]; + amounts: string[]; + logIndex: number; + amountsUSD?: string[]; + pricesUSD?: string[]; + contractInfo?: TokenContractInfo; + tokenMetadata?: Record; +} +``` + +## Networks, types, and errors + +### `Network` + +```typescript +export interface Network { + readonly id: number; + readonly name: string; + readonly nativeTokenSymbol: string; + readonly explorerUrl: string; + readonly displayName: string; +} +``` + +### `Networks` + +```typescript +export declare const Networks: Readonly<{ + mainnet: Readonly<{ + readonly id: 1; + readonly name: "mainnet"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://etherscan.io"; + readonly displayName: "Ethereum"; + }> & Network; + sepolia: Readonly<{ + readonly id: 11155111; + readonly name: "sepolia"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://sepolia.etherscan.io"; + readonly displayName: "Sepolia"; + }> & Network; + polygon: Readonly<{ + readonly id: 137; + readonly name: "polygon"; + readonly nativeTokenSymbol: "POL"; + readonly explorerUrl: "https://polygonscan.com"; + readonly displayName: "Polygon"; + }> & Network; + amoy: Readonly<{ + readonly id: 80002; + readonly name: "amoy"; + readonly nativeTokenSymbol: "POL"; + readonly explorerUrl: "https://amoy.polygonscan.com"; + readonly displayName: "Polygon Amoy"; + }> & Network; + arbitrum: Readonly<{ + readonly id: 42161; + readonly name: "arbitrum"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://arbiscan.io"; + readonly displayName: "Arbitrum"; + }> & Network; + arbitrumSepolia: Readonly<{ + readonly id: 421614; + readonly name: "arbitrum-sepolia"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://sepolia.arbiscan.io"; + readonly displayName: "Arbitrum Sepolia"; + }> & Network; + optimism: Readonly<{ + readonly id: 10; + readonly name: "optimism"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://optimistic.etherscan.io"; + readonly displayName: "Optimism"; + }> & Network; + optimismSepolia: Readonly<{ + readonly id: 11155420; + readonly name: "optimism-sepolia"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://sepolia-optimism.etherscan.io"; + readonly displayName: "Optimism Sepolia"; + }> & Network; + base: Readonly<{ + readonly id: 8453; + readonly name: "base"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://basescan.org"; + readonly displayName: "Base"; + }> & Network; + baseSepolia: Readonly<{ + readonly id: 84532; + readonly name: "base-sepolia"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://sepolia.basescan.org"; + readonly displayName: "Base Sepolia"; + }> & Network; + bsc: Readonly<{ + readonly id: 56; + readonly name: "bsc"; + readonly nativeTokenSymbol: "BNB"; + readonly explorerUrl: "https://bscscan.com"; + readonly displayName: "BSC"; + }> & Network; + bscTestnet: Readonly<{ + readonly id: 97; + readonly name: "bsc-testnet"; + readonly nativeTokenSymbol: "BNB"; + readonly explorerUrl: "https://testnet.bscscan.com"; + readonly displayName: "BSC Testnet"; + }> & Network; + arbitrumNova: Readonly<{ + readonly id: 42170; + readonly name: "arbitrum-nova"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://nova.arbiscan.io"; + readonly displayName: "Arbitrum Nova"; + }> & Network; + avalanche: Readonly<{ + readonly id: 43114; + readonly name: "avalanche"; + readonly nativeTokenSymbol: "AVAX"; + readonly explorerUrl: "https://subnets.avax.network/c-chain"; + readonly displayName: "Avalanche"; + }> & Network; + avalancheTestnet: Readonly<{ + readonly id: 43113; + readonly name: "avalanche-testnet"; + readonly nativeTokenSymbol: "AVAX"; + readonly explorerUrl: "https://subnets-test.avax.network/c-chain"; + readonly displayName: "Avalanche Testnet"; + }> & Network; + katana: Readonly<{ + readonly id: 747474; + readonly name: "katana"; + readonly nativeTokenSymbol: "ETH"; + readonly explorerUrl: "https://katanascan.com"; + readonly displayName: "Katana"; + }> & Network; +}>; +``` + +### `findNetworkById` + +```typescript +export declare function findNetworkById(chainId: number): Network | undefined; +``` + +### `findNetworkByName` + +```typescript +export declare function findNetworkByName(name: string): Network | undefined; +``` + +### `OMSWalletErrorCode` + +```typescript +export type OMSWalletErrorCode = "OMS_HTTP_ERROR" | "OMS_INVALID_RESPONSE" | "OMS_REQUEST_FAILED" | "OMS_AUTH_COMMITMENT_CONSUMED" | "OMS_SESSION_MISSING" | "OMS_SESSION_EXPIRED" | "OMS_WALLET_SELECTION_STALE" | "OMS_WALLET_SELECTION_UNAVAILABLE" | "OMS_WALLET_SELECTION_IN_FLIGHT" | "OMS_TRANSACTION_EXECUTION_UNCONFIRMED" | "OMS_TRANSACTION_STATUS_LOOKUP_FAILED" | "OMS_VALIDATION_ERROR" | "OMS_STORAGE_ERROR"; +``` + +### `OMSWalletUpstreamError` + +```typescript +export interface OMSWalletUpstreamError { + service: "waas" | "indexer"; + name?: string; + code?: number | string; + message?: string; + status?: number; +} +``` + +### `OMSWalletError` + +```typescript +export declare abstract class OMSWalletError extends Error { + readonly code: OMSWalletErrorCode; + readonly operation?: string; + readonly status?: number; + readonly txnId?: string; + readonly retryable?: boolean; + readonly upstreamError?: OMSWalletUpstreamError; + protected constructor(params: { + code: OMSWalletErrorCode; + message: string; + operation?: string; + status?: number; + txnId?: string; + retryable?: boolean; + upstreamError?: OMSWalletUpstreamError; + cause?: unknown; + }); } ``` -Token or native transfer details associated with an indexer transaction entry. - ---- - -### TokenBalancesPage +### `OMSWalletRequestError` ```typescript -interface TokenBalancesPage { - page?: number - column?: string - pageSize?: number - more?: boolean - before?: unknown - after?: unknown - sort?: SortBy[] +export declare class OMSWalletRequestError extends OMSWalletError { + constructor(params: Omit<{ + code: "OMS_HTTP_ERROR" | "OMS_REQUEST_FAILED" | "OMS_AUTH_COMMITMENT_CONSUMED"; + message: string; + operation?: string; + status?: number; + txnId?: string; + retryable?: boolean; + upstreamError?: OMSWalletUpstreamError; + cause?: unknown; + }, "code"> & { + code?: "OMS_HTTP_ERROR" | "OMS_REQUEST_FAILED" | "OMS_AUTH_COMMITMENT_CONSUMED"; + }); } ``` -| Field | Type | Description | -|---|---|---| -| `page` | `number` | Current page index (zero-based). | -| `column` | `string` | Pagination column, when returned or requested. | -| `pageSize` | `number` | Number of entries per page. | -| `more` | `boolean` | `true` if additional pages are available. | -| `before` | `unknown` | Cursor before the current page, when returned. | -| `after` | `unknown` | Cursor after the current page, when returned. | -| `sort` | `SortBy[]` | Optional sort descriptors. | - ---- - -### TokenBalance +### `OMSWalletResponseError` ```typescript -interface TokenBalance { - contractType?: string - contractAddress?: string - accountAddress?: string - tokenId?: string - name?: string - symbol?: string - balance?: string - balanceUSD?: string - priceUSD?: string - priceUpdatedAt?: string - blockHash?: string - blockNumber?: number - chainId?: number - uniqueCollectibles?: string - isSummary?: boolean - contractInfo?: TokenContractInfo - tokenMetadata?: TokenMetadata +export declare class OMSWalletResponseError extends OMSWalletError { + constructor(params: Omit<{ + code: "OMS_INVALID_RESPONSE"; + message: string; + operation?: string; + status?: number; + txnId?: string; + retryable?: boolean; + upstreamError?: OMSWalletUpstreamError; + cause?: unknown; + }, "code"> & { + code?: "OMS_INVALID_RESPONSE"; + }); } ``` -| Field | Type | Description | -|---|---|---| -| `contractType` | `string` | Token standard, e.g. `"ERC20"`, `"ERC721"`, `"ERC1155"`. | -| `contractAddress` | `string` | Address of the token contract. | -| `accountAddress` | `string` | Wallet address this balance belongs to. | -| `tokenId` | `string` | For ERC-721/ERC-1155 tokens, the token ID. | -| `name` | `string` | Token name, when returned directly on the balance row. | -| `symbol` | `string` | Token symbol, when returned directly on the balance row. | -| `balance` | `string` | Balance in the token's smallest denomination. | -| `balanceUSD` | `string` | USD value when returned by the Indexer. | -| `priceUSD` | `string` | Token price in USD when returned by the Indexer. | -| `priceUpdatedAt` | `string` | Timestamp for the returned USD price. | -| `blockHash` | `string` | Block hash at which this balance was recorded. | -| `blockNumber` | `number` | Block number at which this balance was recorded. | -| `chainId` | `number` | Numeric chain ID. | -| `uniqueCollectibles` | `string` | Number of unique collectibles represented by a summary row. | -| `isSummary` | `boolean` | Whether the row represents an aggregated collection summary. | -| `contractInfo` | `TokenContractInfo` | Contract display metadata. ERC-20 decimals are exposed as `contractInfo.decimals`. | -| `tokenMetadata` | `TokenMetadata` | Token-level metadata for NFT/collection entries when returned. | - ---- - -### TokenContractInfo +### `OMSWalletSessionError` ```typescript -interface TokenContractInfo { - chainId?: number - address?: string - source?: string - name?: string - type?: string - symbol?: string - decimals?: number - logoURI?: string - deployed?: boolean - bytecodeHash?: string - extensions?: Record - updatedAt?: string - queuedAt?: string | null - status?: string +export declare class OMSWalletSessionError extends OMSWalletError { + constructor(params: Omit<{ + code: "OMS_SESSION_MISSING" | "OMS_SESSION_EXPIRED"; + message: string; + operation?: string; + status?: number; + txnId?: string; + retryable?: boolean; + upstreamError?: OMSWalletUpstreamError; + cause?: unknown; + }, "code"> & { + code?: "OMS_SESSION_MISSING" | "OMS_SESSION_EXPIRED"; + }); } ``` -Contract-level metadata returned by the Indexer when `includeMetadata` is `true`. - ---- - -### TokenMetadata +### `OMSWalletTransactionError` ```typescript -interface TokenMetadata { - chainId?: number - contractAddress?: string - tokenId?: string - source?: string - name?: string - description?: string - image?: string - video?: string - audio?: string - properties?: Record - attributes?: Record[] - image_data?: string - external_url?: string - background_color?: string - animation_url?: string - decimals?: number - updatedAt?: string - assets?: TokenMetadataAsset[] - status?: string - queuedAt?: string | null - lastFetched?: string +export declare class OMSWalletTransactionError extends OMSWalletError { + constructor(params: Omit<{ + code: "OMS_TRANSACTION_EXECUTION_UNCONFIRMED" | "OMS_TRANSACTION_STATUS_LOOKUP_FAILED"; + message: string; + operation?: string; + status?: number; + txnId?: string; + retryable?: boolean; + upstreamError?: OMSWalletUpstreamError; + cause?: unknown; + }, "code"> & { + code?: "OMS_TRANSACTION_EXECUTION_UNCONFIRMED" | "OMS_TRANSACTION_STATUS_LOOKUP_FAILED"; + }); } ``` -Token-level metadata returned by the Indexer when available. - ---- - -### TokenMetadataAsset +### `OMSWalletSelectionError` ```typescript -interface TokenMetadataAsset { - id?: number - collectionId?: number - tokenId?: string - url?: string - metadataField?: string - name?: string - filesize?: number - mimeType?: string - width?: number - height?: number - updatedAt?: string +export declare class OMSWalletSelectionError extends OMSWalletError { + constructor(params: { + code: "OMS_WALLET_SELECTION_STALE" | "OMS_WALLET_SELECTION_UNAVAILABLE" | "OMS_WALLET_SELECTION_IN_FLIGHT"; + message: string; + operation?: string; + status?: number; + txnId?: string; + retryable?: boolean; + upstreamError?: OMSWalletUpstreamError; + cause?: unknown; + }); } ``` -Media asset metadata associated with token metadata when returned. - ---- - -### Contract Call Arguments +### `OMSWalletValidationError` ```typescript -{ - type: string - value: unknown +export declare class OMSWalletValidationError extends OMSWalletError { + constructor(params: Omit<{ + code: "OMS_VALIDATION_ERROR"; + message: string; + operation?: string; + status?: number; + txnId?: string; + retryable?: boolean; + upstreamError?: OMSWalletUpstreamError; + cause?: unknown; + }, "code"> & { + code?: "OMS_VALIDATION_ERROR"; + }); } ``` -A loosely-typed ABI argument object used by [`callContract`](#callcontract). For fully-typed encoding, use the ABI overload of [`sendTransaction`](#abi-encoded-contract-call) instead. - -| Field | Type | Description | -|---|---|---| -| `type` | `string` | Solidity type string, e.g. `"address"`, `"uint256"`, `"bytes32"`, `"bool"`. | -| `value` | `unknown` | The argument value. Use a string for large integers to avoid precision loss. | - ---- - -### AuthMode +### `OMSWalletStorageError` ```typescript -enum AuthMode { - OTP = 'otp', - IDToken = 'id-token', - AuthCode = 'auth-code', - AuthCodePKCE = 'auth-code-pkce' +export declare class OMSWalletStorageError extends OMSWalletError { + constructor(params: Omit<{ + code: "OMS_STORAGE_ERROR"; + message: string; + operation?: string; + status?: number; + txnId?: string; + retryable?: boolean; + upstreamError?: OMSWalletUpstreamError; + cause?: unknown; + }, "code"> & { + code?: "OMS_STORAGE_ERROR"; + }); } ``` -OIDC provider configs support `AuthMode.AuthCode` and `AuthMode.AuthCodePKCE`. Redirect auth defaults to `AuthMode.AuthCodePKCE` when a provider does not specify `authMode`. - ---- - -### WalletType +### `isOMSWalletError` ```typescript -enum WalletType { - Ethereum = 'ethereum' -} +export declare function isOMSWalletError(error: unknown): error is OMSWalletError; ``` - -Identifies the wallet type to load or create. Accepted by wallet creation and auth completion flows, including [`completeEmailAuth`](#completeemailauth), [`startOidcRedirectAuth`](#startoidcredirectauth), [`signInWithOidcRedirect`](#signinwithoidcredirect), and [`createWallet`](#createwallet). Defaults to `WalletType.Ethereum`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PUBLISHING.md b/PUBLISHING.md index 51d5735..d7ae3e2 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -1,25 +1,25 @@ # Publishing The SDK and wagmi connector release in lockstep. The connector source manifest keeps -`@0xsequence/typescript-sdk` as `workspace:*` in both `peerDependencies` and `devDependencies`. -This gives local development a workspace link, and `pnpm pack` / `pnpm publish` rewrites the -published peer dependency to the exact release version. +`@polygonlabs/oms-wallet` as `workspace:^` in `peerDependencies` and `workspace:*` in +`devDependencies`. This gives local development a workspace link, and `pnpm pack` / `pnpm publish` +rewrites the published peer dependency to a semver range for the release version. Do not replace the connector's SDK peer with a literal version in source, and do not publish with -`npm publish`. Use pnpm from the workspace root so the `workspace:*` protocol is rewritten before +`npm publish`. Use pnpm from the workspace root so the `workspace:` protocol is rewritten before the package reaches npm. ## Before Merging The Release PR -Before publishing a new alpha version, update these values to the same exact version: +Before publishing a new stable version, update these values to the same exact version: - `package.json` `version` - `packages/oms-wallet-wagmi-connector/package.json` `version` -Leave these values as `workspace:*`: +Leave these values as workspace protocols: -- `packages/oms-wallet-wagmi-connector/package.json` `peerDependencies["@0xsequence/typescript-sdk"]` -- `packages/oms-wallet-wagmi-connector/package.json` `devDependencies["@0xsequence/typescript-sdk"]` +- `packages/oms-wallet-wagmi-connector/package.json` `peerDependencies["@polygonlabs/oms-wallet"]`: `workspace:^` +- `packages/oms-wallet-wagmi-connector/package.json` `devDependencies["@polygonlabs/oms-wallet"]`: `workspace:*` ## After The Release PR Is Merged @@ -31,67 +31,106 @@ git pull pnpm install --frozen-lockfile ``` -2. Capture the release version and verify package metadata: +2. Capture the release version and verify the SDK: ```bash VERSION=$(node -p "require('./package.json').version") -pnpm check:package-versions +pnpm verify --stable ``` -3. Run release checks: +This is the same command CI runs, with the additional stable-version check. It typechecks and tests +the SDK and connector, builds every example, packs both publishable packages, checks their contents, +and verifies that pnpm rewrites the connector's `workspace:` dependencies to the release version. -```bash -pnpm test -pnpm --filter @0xsequence/oms-wallet-wagmi-connector test -pnpm --filter @0xsequence/oms-wallet-wagmi-connector build -pnpm build -pnpm build:node-example -pnpm build:node-contract-deploy-example -pnpm build:example -pnpm build:trails-actions-example -pnpm build:wagmi-example -``` - -4. Dry-run the filtered workspace publish: +3. Dry-run the release: ```bash -pnpm --filter @0xsequence/typescript-sdk \ - --filter @0xsequence/oms-wallet-wagmi-connector \ - publish --dry-run --no-git-checks --tag alpha --access public +pnpm release:dry-run ``` If the dry run reports no new packages, the version is already published. Stop and verify the intended release version before continuing. -5. Log in to npm if needed: +4. Log in to npm if needed: ```bash pnpm npm login pnpm npm whoami ``` -6. Publish both workspace packages from the root: +5. Publish both workspace packages from the root: ```bash -pnpm --filter @0xsequence/typescript-sdk \ - --filter @0xsequence/oms-wallet-wagmi-connector \ - publish --tag alpha --access public +pnpm release:publish ``` If the filtered publish is interrupted after the SDK is published, rerun the connector publish with pnpm: ```bash -pnpm --filter @0xsequence/oms-wallet-wagmi-connector publish --tag alpha --access public +pnpm --filter @polygonlabs/oms-wallet-wagmi-connector publish --access public ``` -7. Verify published versions and alpha dist tags: +6. Verify published versions and latest dist tags: ```bash -pnpm view @0xsequence/typescript-sdk@$VERSION version -pnpm view @0xsequence/oms-wallet-wagmi-connector@$VERSION version -pnpm view @0xsequence/typescript-sdk@alpha version -pnpm view @0xsequence/oms-wallet-wagmi-connector@alpha version +pnpm view @polygonlabs/oms-wallet@$VERSION version +pnpm view @polygonlabs/oms-wallet-wagmi-connector@$VERSION version +pnpm view @polygonlabs/oms-wallet@latest version +pnpm view @polygonlabs/oms-wallet-wagmi-connector@latest version ``` -Optional: create a git tag and GitHub release for `v$VERSION`. +7. Create a git tag and GitHub release for `v$VERSION`. + +## Alpha, Beta, And Snapshot Releases + +Use the stable flow above for normal releases. Use this section only when you intentionally want a +non-`latest` npm dist tag. + +1. Set both publishable package versions to the same prerelease version: + +```bash +# Examples: +# 0.2.1-alpha.0 +# 0.2.1-beta.0 +# 0.2.1-snapshot.20260703.0 +``` + +Update: + +- `package.json` `version` +- `packages/oms-wallet-wagmi-connector/package.json` `version` + +Then capture and verify the prerelease: + +```bash +VERSION=$(node -p "require('./package.json').version") +pnpm verify +``` + +2. Dry-run with the matching npm tag: + +```bash +pnpm release:dry-run --tag alpha +``` + +Use `--tag beta` for beta builds and `--tag snapshot` for snapshot builds. + +3. Publish with the same tag used in the dry run: + +```bash +pnpm release:publish --tag alpha +``` + +4. Verify the exact version and dist tag: + +```bash +pnpm view @polygonlabs/oms-wallet@$VERSION version +pnpm view @polygonlabs/oms-wallet-wagmi-connector@$VERSION version +pnpm view @polygonlabs/oms-wallet@alpha version +pnpm view @polygonlabs/oms-wallet-wagmi-connector@alpha version +``` + +Replace `alpha` with `beta` or `snapshot` for those release types. + +Do not leave prerelease versions or prerelease npm tags in source when preparing a stable release. diff --git a/README.md b/README.md index 87b4fb4..5d3ffcc 100644 --- a/README.md +++ b/README.md @@ -1,196 +1,112 @@ -# OMS Client TypeScript SDK +# OMS Wallet TypeScript SDK -A TypeScript SDK for the OMS (Open Money Stack) platform. Provides email and OIDC redirect wallet authentication, on-chain transaction submission, message signing, and token balance queries — with automatic session persistence. +Build non-custodial EVM wallet experiences in TypeScript with OMS Wallet: email and OIDC auth, session restore, message signing, transaction submission, and token balance queries. -## Usage +[API reference](https://docs.polygon.technology/wallets/sdk/typescript/api-reference) + +**Requirements:** Node.js 22+ for Node runtimes and local builds. Browser apps +need a modern browser with WebCrypto support. + +## Before You Start + +- Use an OMS publishable key for your project. Use sandbox/dev keys for local development and testnet flows. +- Browser apps work out of the box. Node.js and other custom runtimes should provide custom storage when they need persistent sessions. +- Start with a sign-in, sign-message, or balance-read flow. Transaction examples below use Polygon Amoy; mainnet transactions can move real funds. + +## Installation Install the published SDK package in your application: ```bash -pnpm add @0xsequence/typescript-sdk +pnpm add @polygonlabs/oms-wallet ``` For npm or yarn projects: ```bash -npm install @0xsequence/typescript-sdk -yarn add @0xsequence/typescript-sdk +npm install @polygonlabs/oms-wallet +yarn add @polygonlabs/oms-wallet ``` -Then initialize the client with your OMS publishable key: +Then initialize OMS Wallet with your OMS publishable key: ```typescript -import { OMSClient } from '@0xsequence/typescript-sdk' +import { OMSWallet } from '@polygonlabs/oms-wallet' -const oms = new OMSClient({ +const omsWallet = new OMSWallet({ publishableKey: 'your-publishable-key', }) ``` -The SDK derives the WaaS API and IndexerGateway endpoints from the publishable key prefix. - -In Vite browser apps, keep the publishable key in local environment variables: - -```typescript -function requiredEnv(name: string, value: string | undefined): string { - if (!value) { - throw new Error(`Missing ${name}`) - } - return value -} - -const oms = new OMSClient({ - publishableKey: requiredEnv('VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY), -}) -``` - -If your app imports utilities from `viem`, such as the `parseUnits` helper used in the quick start below, install it as a direct dependency too: - -```bash -pnpm add viem -``` - -For local development in this repository, install dependencies and build the workspace package: - -From the repository root: - -```bash -pnpm install -pnpm build -``` - -## React Example - -A deployed React example is available at [https://0xsequence.github.io/typescript-sdk/react-example/](https://0xsequence.github.io/typescript-sdk/react-example/). - -To run it locally from the repository root: - -```bash -cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY in examples/react/.env.local -pnpm dev:example -``` - -## Wagmi Connector - -This workspace also includes `@0xsequence/oms-wallet-wagmi-connector`, an ESM-only package that adapts an -active OMS client to wagmi's connector API. - -```bash -pnpm --filter @0xsequence/oms-wallet-wagmi-connector build -pnpm --filter @0xsequence/oms-wallet-wagmi-connector test -``` - -See [packages/oms-wallet-wagmi-connector/README.md](./packages/oms-wallet-wagmi-connector/README.md) for usage. - -## Wagmi React Example - -The Wagmi example uses `@0xsequence/oms-wallet-wagmi-connector`, wagmi's MetaMask connector, and the Trails widget. - -The deployed Wagmi example is available at [https://0xsequence.github.io/typescript-sdk/wagmi-example/](https://0xsequence.github.io/typescript-sdk/wagmi-example/). - -To run it locally from the repository root: - -```bash -cp examples/wagmi/.env.example examples/wagmi/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY in examples/wagmi/.env.local -# Replace VITE_TRAILS_API_KEY only if you need a different Trails project -pnpm dev:wagmi-example -``` - -## Trails Actions React Example - -The Trails Actions example prepares and sends Polygon swap, Earn deposit, swap plus Earn deposit, and Earn withdrawal flows with `0xtrails/actions`. - -The deployed Trails Actions example is available at [https://0xsequence.github.io/typescript-sdk/trails-actions-example/](https://0xsequence.github.io/typescript-sdk/trails-actions-example/). - -To run it locally from the repository root: - -```bash -cp examples/trails-actions/.env.example examples/trails-actions/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY in examples/trails-actions/.env.local -pnpm dev:trails-actions-example -``` - -## Node Example - -The Node example walks through email OTP sign-in and message signing from a terminal. - -To run it locally from the repository root: - -```bash -OMS_PUBLISHABLE_KEY=your-publishable-key pnpm dev:node-example -``` - -## Node Contract Deploy Example - -The Node contract deploy example compiles a small ERC-20 contract and submits a Polygon Amoy deployment transaction through the OMS wallet API. - -To run it locally from the repository root: - -```bash -cp examples/node-contract-deploy-example/.env.example examples/node-contract-deploy-example/.env.local -# Fill OMS_PUBLISHABLE_KEY in examples/node-contract-deploy-example/.env.local -pnpm dev:node-contract-deploy-example -``` +The SDK derives the wallet API and indexer endpoints from the publishable key prefix. ## Quick Start ```typescript -import { FeeOptionSelector, Networks, OMSClient, WalletType } from '@0xsequence/typescript-sdk' -import { parseUnits } from 'viem' +import { Networks, OMSWallet } from '@polygonlabs/oms-wallet' -const oms = new OMSClient({ +const omsWallet = new OMSWallet({ publishableKey: 'your-publishable-key', }) // 1. Send a one-time code to the user's email -await oms.wallet.startEmailAuth({ email: 'user@example.com' }) +await omsWallet.wallet.startEmailAuth({ email: 'user@example.com' }) -// 2. User enters the code — verifies it and sets up the wallet automatically -const { walletAddress, credential } = await oms.wallet.completeEmailAuth({ code: '123456' }) +// 2. User enters the code from their inbox. +const { walletAddress } = await omsWallet.wallet.completeEmailAuth({ code: '123456' }) // 3. The wallet is ready console.log('Wallet address:', walletAddress) -console.log('Credential:', credential.credentialId) -// 4. Send a transaction -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, - to: '0x1111111111111111111111111111111111111111', - value: parseUnits('1', 18), // 1 POL - // If this Polygon mainnet transaction is not sponsored, choose the first fee token the wallet can pay. - selectFeeOption: FeeOptionSelector.firstAvailable, +// 4. Prove the wallet can sign without moving funds. +const signature = await omsWallet.wallet.signMessage({ + network: Networks.amoy, + message: 'hello from OMS Wallet', }) -console.log(tx.txnHash ?? tx.txnId) +console.log('Signature:', signature) + +// 5. Read balances from the chains your app needs. +const balances = await omsWallet.indexer.getBalances({ + walletAddress, + networks: [Networks.polygon, Networks.base, Networks.arbitrum], + includeMetadata: true, +}) +console.log('Balances:', balances) ``` ## Overview -`OMSClient` exposes two sub-clients: +`OMSWallet` exposes two sub-clients: | Property | Type | Description | |---|---|---| -| `oms.wallet` | `WalletClient` | Authentication, signing, and transaction submission. | -| `oms.indexer` | `IndexerClient` | Read token balances and on-chain state. | +| `omsWallet.wallet` | `OMSWalletClient` | Authentication, signing, and transaction submission. | +| `omsWallet.indexer` | `OMSWalletIndexerClient` | Read token balances and on-chain state. | + +## Security Model + +The SDK stores completed wallet-session metadata in the configured storage so apps can restore an active session after refresh or restart. Pending email OTP and OIDC redirect state are transient and are not exposed through `session`. + +In browsers, wallet API requests are signed with a non-extractable WebCrypto P-256 credential. Persisted session data contains wallet and auth metadata only; the browser credential remains managed by WebCrypto. Non-browser runtimes fall back to in-memory storage unless you provide a custom `StorageManager`. + +Completed auth requests ask for a one-week session lifetime by default. You can request a shorter or longer lifetime up to 30 days. Expired sessions become inactive before protected wallet operations; call `signOut()` to end the session and clear active wallet state. ## Authentication Flow -OMS supports email-based OTP and OIDC authorization-code redirect auth. +OMS supports email-based OTP, OIDC ID-token auth, and OIDC authorization-code redirect auth. ### Email OTP Auth Email OTP is a two-step flow: -1. **`startEmailAuth({ email })`** — clears any active session and sends a one-time code to the user's inbox. +1. **`startEmailAuth({ email, sessionLifetimeSeconds? })`** — validates the requested session lifetime, clears any active session, and sends a one-time code to the user's inbox. 2. **`completeEmailAuth({ code })`** — verifies the code, then automatically loads an existing wallet or creates a new one if none exists. Returns `{ walletAddress, wallet, wallets, credential }`. Use manual wallet selection when the app needs to present wallet choices: ```typescript -const selection = await oms.wallet.completeEmailAuth({ +const selection = await omsWallet.wallet.completeEmailAuth({ code: '123456', - walletType: WalletType.Ethereum, walletSelection: 'manual', }) @@ -203,79 +119,119 @@ The returned pending selection is bound to the verified auth flow and signer. Ho ### OIDC Redirect Auth -Google and Apple redirect auth are configured by default. The redirect auth APIs are provider-neutral, so `auth.oidcProviders` can replace the configured provider set when you need custom providers. +For simple browser apps, call `signInWithOidcRedirect` from a sign-in action. +Pass one of the immutable `OmsRelayOidcProviders` values for Google or Apple. +For these OMS-relayed providers, the method calls `startOidcRedirectAuth`, derives the +current page as `omsRelayReturnUri`, and navigates with `window.location.assign`: ```typescript -const oms = new OMSClient({ - publishableKey: 'your-publishable-key', -}) +import { OmsRelayOidcProviders } from '@polygonlabs/oms-wallet' + +void omsWallet.wallet.signInWithOidcRedirect({ provider: OmsRelayOidcProviders.google }) +void omsWallet.wallet.signInWithOidcRedirect({ provider: OmsRelayOidcProviders.apple }) + +// On the callback page: +const result = await omsWallet.wallet.completeOidcRedirectAuth() +if (result) { + console.log('Wallet address:', result.walletAddress) +} ``` For router-driven apps, use the explicit start/complete methods: ```typescript -const { url } = await oms.wallet.startOidcRedirectAuth({ - provider: 'google', - redirectUri: `${window.location.origin}/auth/callback`, // optional in browser apps +const { authorizationUrl } = await omsWallet.wallet.startOidcRedirectAuth({ + provider: OmsRelayOidcProviders.google, + omsRelayReturnUri: `${window.location.origin}/auth/callback`, // optional in browser apps }) -window.location.assign(url) +window.location.assign(authorizationUrl) // On the callback route: -const result = await oms.wallet.completeOidcRedirectAuth() +const result = await omsWallet.wallet.completeOidcRedirectAuth() if (result) { console.log('Wallet address:', result.walletAddress) } ``` -OIDC redirect auth also supports manual wallet selection by passing `walletSelection: 'manual'` to `startOidcRedirectAuth` or `completeOidcRedirectAuth`. Options passed at start are stored with the pending redirect state and used after the provider redirects back. +OIDC redirect auth also supports manual wallet selection by passing +`walletSelection: 'manual'` to `startOidcRedirectAuth` or +`completeOidcRedirectAuth`. Options passed at start are stored with the pending +redirect state and used after the provider redirects back. -For simple browser apps, use `signInWithOidcRedirect` from a sign-in action. It calls `startOidcRedirectAuth`, derives the current page as `redirectUri`, and navigates with `window.location.assign`: +For SDK built-in Google and Apple providers, `omsRelayReturnUri` is the URL where the OMS relay returns the user after Google or Apple redirects to the OMS callback. In browser convenience flows, the SDK derives it from the current page URL when omitted. Custom OIDC providers do not use `omsRelayReturnUri`; configure their OAuth callback with `providerRedirectUri` instead: -```typescript -void oms.wallet.signInWithOidcRedirect({ provider: 'google' }) -void oms.wallet.signInWithOidcRedirect({ provider: 'apple' }) +| Flow | Provider config | App return URL | Provider OAuth callback | +|---|---|---|---| +| OMS relay Google/Apple | `OmsRelayOidcProviders.google` / `.apple` | `omsRelayReturnUri` | OMS relay callback derived as `{apiBase}/auth/waas/callback/{google|apple}` | +| Custom OIDC provider | `CustomOidcProviderConfig` | `providerRedirectUri` | `providerRedirectUri` | +| Google/Apple without SDK relay | Direct custom config for Google or Apple | `providerRedirectUri` | `providerRedirectUri` | -// On the callback page: -const result = await oms.wallet.completeOidcRedirectAuth() -if (result) { - console.log('Wallet address:', result.walletAddress) -} -``` - -Pass `loginHint` only when you want to prefill or select a specific Google account, such as during session-expiry reauth. The SDK only sends `login_hint` for Google providers. When omitted, the SDK falls back to the previous active session email when one exists before the redirect auth attempt starts. After `signOut()`, that previous session email is cleared. To force no `login_hint` for a call, pass `loginHint: ''`. +Pass `loginHint` only when you want to prefill or select a specific Google +account, such as during session-expiry reauth. When omitted, the SDK falls back +to the previous active session email when one exists before the redirect auth +attempt starts. To force no `login_hint` for a call, pass `loginHint: ''`. Pending redirect state is stored in `sessionStorage` by default. Final wallet session metadata continues to use the configured SDK storage. -`googleOidcProvider()` uses the SDK default Google client ID, the SDK relay redirect URI, `openid email profile` scopes, and PKCE auth-code mode by default. +`OmsRelayOidcProviders.google` and `OmsRelayOidcProviders.apple` are deeply +frozen, opaque SDK values. Their client IDs, scopes, authorization parameters, +and PKCE auth-code mode are not caller-editable. The SDK derives their provider +callback URL from the publishable-key environment as +`{apiBase}/auth/waas/callback/{provider}`. +For custom providers, see [Custom OIDC Providers](#custom-oidc-providers). + +### OIDC ID-Token Auth + +For OIDC ID-token flows, obtain the provider token in your app or backend flow, +then pass it to the SDK with the issuer and audience: + +```typescript +const result = await omsWallet.wallet.signInWithOidcIdToken({ + idToken: googleIdToken, + issuer: 'https://accounts.google.com', + audience: 'YOUR_WEB_CLIENT_ID', +}) + +console.log('Wallet address:', result.walletAddress) +``` -`appleOidcProvider()` uses the SDK default Apple Services ID, the SDK relay redirect URI, `openid email` scopes, `response_mode=form_post`, and PKCE auth-code mode by default. +Use `walletSelection: 'manual'` with `signInWithOidcIdToken` when your app needs +to present its own wallet picker after the token is verified. ### Session State -Email and OIDC auth both persist the active wallet session in the configured SDK storage. Browser storage defaults to `localStorage` when available; non-browser runtimes fall back to in-memory storage unless you provide a custom `StorageManager`. Browser signing defaults to a non-extractable WebCrypto P-256 credential using `ecdsa-p256-sha256`, so the private session key is not written to `localStorage`. Completed auth requests ask WaaS for a one-week session lifetime. +Email and OIDC auth both persist the active wallet session in the configured SDK storage. The versioned session record is scoped to the publishable key project and API environment. A client rejects and clears a stored session when either scope differs. Browser storage defaults to `localStorage` when available; non-browser runtimes fall back to in-memory storage unless you provide a custom `StorageManager`. Browser signing defaults to a non-extractable WebCrypto P-256 credential using `ecdsa-p256-sha256`. Completed auth requests ask the wallet API for a one-week session lifetime. -Pass `sessionLifetimeSeconds` to `completeEmailAuth`, `startOidcRedirectAuth`, `completeOidcRedirectAuth`, or `signInWithOidcRedirect` to request a different session lifetime. For OIDC redirects, values passed at start are stored with the pending redirect state and used on callback completion unless completion overrides them. +Pass `sessionLifetimeSeconds` to `startEmailAuth`, `signInWithOidcIdToken`, `startOidcRedirectAuth`, `completeOidcRedirectAuth`, or `signInWithOidcRedirect` to request a different session lifetime. Values must be integer seconds from `1` through `2592000` (30 days). For OIDC redirects, values passed at start are stored with the pending redirect state and used on callback completion unless completion overrides them. -Use `oms.wallet.walletAddress` when you only need the active wallet address. Use `oms.wallet.session` when you also need credential expiry, login type, or the email returned by the wallet API. +Use `omsWallet.wallet.walletAddress` when you only need the active wallet address. Use `omsWallet.wallet.session` when you also need credential expiry or structured auth metadata. ```typescript -const walletAddress = oms.wallet.walletAddress -const { expiresAt, loginType, sessionEmail } = oms.wallet.session +const walletAddress = omsWallet.wallet.walletAddress +const { expiresAt, auth } = omsWallet.wallet.session +const accountEmail = auth?.email +const authLabel = auth?.type === 'oidc' + ? auth.providerLabel ?? auth.provider ?? auth.issuer + : auth?.type === 'email' + ? 'Email' + : 'Unknown' ``` -Use `oms.wallet.getIdToken({ ttlSeconds, customClaims })` to request an ID token for the active wallet session. +The `session` value is a readonly snapshot. Changing the returned object does not update SDK state or persisted session metadata. + +Use `omsWallet.wallet.getIdToken({ ttlSeconds, customClaims })` to request an ID token for the active wallet session. Pending email OTP and OIDC redirect state are not exposed through `session`; use the auth method results to drive pending UI. -The SDK makes expired sessions inactive before protected wallet operations and throws `OmsSessionError` with code `OMS_SESSION_EXPIRED`. It clears the active signer/session state, but keeps the expired session metadata in storage until the app explicitly starts a new auth flow or calls `signOut()`. Subscribe with `oms.wallet.onSessionExpired` to route the user back to sign-in while preserving the expired session snapshot for email OTP reauth or Google account hints, including after a page refresh: +The SDK makes expired sessions inactive before protected wallet operations and throws `OMSWalletSessionError` with code `OMS_SESSION_EXPIRED`. It clears the active signer/session state, but keeps the expired session metadata in storage until the app explicitly starts a new auth flow or calls `signOut()`. Subscribe with `omsWallet.wallet.onSessionExpired` to route the user back to sign-in while preserving the expired session snapshot for email OTP reauth or Google account hints, including after a page refresh: ```typescript -const oms = new OMSClient({ +const omsWallet = new OMSWallet({ publishableKey: 'your-publishable-key', }) -const unsubscribe = oms.wallet.onSessionExpired(({ session }) => { +const unsubscribe = omsWallet.wallet.onSessionExpired(({ session }) => { showReauth(session) }) ``` @@ -283,88 +239,122 @@ const unsubscribe = oms.wallet.onSessionExpired(({ session }) => { To end the session, call: ```typescript -await oms.wallet.signOut() +await omsWallet.wallet.signOut() ``` -## Errors +## Core Workflows -Public methods throw `OmsSdkError` subclasses with stable SDK fields such as `code`, `operation`, `status`, and `retryable`. When a failure comes from a remote OMS service response or transport failure, the error also includes `upstreamError` with normalized WaaS or indexer details for logging and service-specific troubleshooting. Application logic should usually branch on the SDK-level `code`. +### Sign and Verify Messages -For transaction writes, `OMS_TRANSACTION_EXECUTION_UNCONFIRMED` means the SDK has a `txnId` from preparation, but the execute request failed before the SDK could confirm whether the transaction was submitted; do not blindly resend the same write. `OMS_TRANSACTION_STATUS_LOOKUP_FAILED` means the transaction was submitted but status polling failed, so retry status lookup with the returned `txnId`. `retryable` describes the failed SDK operation, not the whole user intent. +```typescript +const signature = await omsWallet.wallet.signMessage({ + network: Networks.amoy, + message: 'some message to sign', +}) + +const isValid = await omsWallet.wallet.isValidMessageSignature({ + network: Networks.amoy, + walletAddress: omsWallet.wallet.walletAddress, + message: 'some message to sign', + signature, +}) +``` + +### Sign and Verify Typed Data ```typescript -import { OmsSdkError } from '@0xsequence/typescript-sdk' +const signature = await omsWallet.wallet.signTypedData({ + network: Networks.amoy, + typedData, +}) -try { - await oms.wallet.startEmailAuth({ email: 'user@example.com' }) -} catch (err) { - if (err instanceof OmsSdkError) { - console.log(err.code, err.operation, err.upstreamError) - } -} +const isValid = await omsWallet.wallet.isValidTypedDataSignature({ + network: Networks.amoy, + walletAddress: omsWallet.wallet.walletAddress, + typedData, + signature, +}) ``` -## Networks +### Query Balances -The SDK exports `Networks`, `supportedNetworks`, `findNetworkById(id)`, and `findNetworkByName(name)` for the networks currently configured by OMS. Each network has `id`, `name`, `nativeTokenSymbol`, `explorerUrl`, and `displayName`. `name` is the registry/routing slug, while `displayName` is the user-facing label. +```typescript +const { walletAddress } = omsWallet.wallet +if (!walletAddress) throw new Error('No active wallet session') -The `network` parameter on all transaction and signing methods accepts a `Network` from the SDK registry: +const result = await omsWallet.indexer.getBalances({ + networks: [Networks.polygon, Networks.base, Networks.arbitrum], + walletAddress, + includeMetadata: true, +}) + +for (const b of result.nativeBalances) { + console.log(b.symbol, b.balance) +} + +for (const b of result.balances) { + console.log(b.contractInfo?.symbol, b.balance, b.contractInfo?.decimals) +} +``` + +Pass `contractAddresses` to filter balances to specific token contracts. Omit `networks` to query mainnets by default, or pass `networkType: 'TESTNETS'` / `'ALL'`. With `includeMetadata: true`, ERC-20 decimals are available as `contractInfo.decimals`. The response is paginated; pass `page` when requesting later pages. + +### Query Transaction History ```typescript -import { Networks, findNetworkById, supportedNetworks } from '@0xsequence/typescript-sdk' +const { walletAddress } = omsWallet.wallet +if (!walletAddress) throw new Error('No active wallet session') -await oms.wallet.signMessage({ network: Networks.polygon, message: 'some message to sign' }) +const history = await omsWallet.indexer.getTransactionHistory({ + walletAddress, + networks: [Networks.polygon, Networks.base, Networks.arbitrum], + includeMetadata: true, +}) -console.log(supportedNetworks) -console.log(findNetworkById(80002)) // Networks.amoy +for (const transaction of history.transactions) { + console.log(transaction.txnHash, transaction.timestamp) +} ``` -| Key | id | name | display name | native token | explorerUrl | -|---|---:|---|---|---|---| -| `Networks.mainnet` | 1 | `mainnet` | Ethereum | ETH | `https://etherscan.io` | -| `Networks.sepolia` | 11155111 | `sepolia` | Sepolia | ETH | `https://sepolia.etherscan.io` | -| `Networks.polygon` | 137 | `polygon` | Polygon | POL | `https://polygonscan.com` | -| `Networks.amoy` | 80002 | `amoy` | Polygon Amoy | POL | `https://amoy.polygonscan.com` | -| `Networks.arbitrum` | 42161 | `arbitrum` | Arbitrum | ETH | `https://arbiscan.io` | -| `Networks.arbitrumSepolia` | 421614 | `arbitrum-sepolia` | Arbitrum Sepolia | ETH | `https://sepolia.arbiscan.io` | -| `Networks.optimism` | 10 | `optimism` | Optimism | ETH | `https://optimistic.etherscan.io` | -| `Networks.optimismSepolia` | 11155420 | `optimism-sepolia` | Optimism Sepolia | ETH | `https://sepolia-optimism.etherscan.io` | -| `Networks.base` | 8453 | `base` | Base | ETH | `https://basescan.org` | -| `Networks.baseSepolia` | 84532 | `base-sepolia` | Base Sepolia | ETH | `https://sepolia.basescan.org` | -| `Networks.bsc` | 56 | `bsc` | BSC | BNB | `https://bscscan.com` | -| `Networks.bscTestnet` | 97 | `bsc-testnet` | BSC Testnet | BNB | `https://testnet.bscscan.com` | -| `Networks.arbitrumNova` | 42170 | `arbitrum-nova` | Arbitrum Nova | ETH | `https://nova.arbiscan.io` | -| `Networks.avalanche` | 43114 | `avalanche` | Avalanche | AVAX | `https://subnets.avax.network/c-chain` | -| `Networks.avalancheTestnet` | 43113 | `avalanche-testnet` | Avalanche Testnet | AVAX | `https://subnets-test.avax.network/c-chain` | -| `Networks.katana` | 747474 | `katana` | Katana | ETH | `https://katanascan.com` | +### Sending Transactions + +`sendTransaction` can move real funds on mainnet. Start on a testnet such as Polygon Amoy, fund the wallet from a faucet, and use a small value before switching to production networks. -## Sending Transactions +Install `viem` when using `parseUnits` for transaction values: + +```bash +pnpm add viem +``` `sendTransaction` has three overloaded signatures to cover the most common patterns. -### Native Token Transfer +#### First Testnet Transfer ```typescript +import { FeeOptionSelector, Networks } from '@polygonlabs/oms-wallet' import { parseUnits } from 'viem' -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, +const tx = await omsWallet.wallet.sendTransaction({ + network: Networks.amoy, to: '0x1111111111111111111111111111111111111111', - value: parseUnits('1', 18), // 1 POL + value: parseUnits('0.001', 18), // 0.001 testnet POL + selectFeeOption: FeeOptionSelector.firstAvailable, }) + +console.log(tx.txnHash ?? tx.txnId) ``` -### Raw Data Transaction +#### Raw Data Transaction ```typescript -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, +const tx = await omsWallet.wallet.sendTransaction({ + network: Networks.amoy, to: '0x2222222222222222222222222222222222222222', data: '0x12345678', }) ``` -### ABI-Encoded Contract Call (via viem) +#### ABI-Encoded Contract Call (via viem) Pass an ABI and function name — the SDK encodes the calldata automatically using viem. @@ -382,18 +372,36 @@ const erc20Abi = [ }, ] as const -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, +const tx = await omsWallet.wallet.sendTransaction({ + network: Networks.amoy, to: '0x3333333333333333333333333333333333333333', abi: erc20Abi, functionName: 'transfer', - args: ['0x1111111111111111111111111111111111111111', parseUnits('1', 18)], + args: ['0x1111111111111111111111111111111111111111', parseUnits('0.001', 18)], }) ``` -`sendTransaction` prepares and executes the transaction, then polls WaaS for +#### Call a Contract (method string + args) + +```typescript +import { parseUnits } from 'viem' + +const tx = await omsWallet.wallet.callContract({ + network: Networks.amoy, + contractAddress: '0x3333333333333333333333333333333333333333', + method: 'transfer(address,uint256)', + args: [ + { type: 'address', value: '0x1111111111111111111111111111111111111111' }, + { type: 'uint256', value: parseUnits('0.001', 18).toString() }, + ], +}) +``` + +`sendTransaction` and `callContract` prepare and execute the transaction, then poll the wallet API for the latest transaction status. The response includes `txnId`, `status`, and `txnHash` -when the transaction has been published. +when the transaction has been published. `statusResolution` is `resolved` when +polling finds a terminal status or transaction hash, and `timed-out` when the +polling deadline is reached. To return immediately after execute without status polling, pass `waitForStatus: false`. You can then call `getTransactionStatus` with the @@ -402,23 +410,25 @@ returned `txnId`. ```typescript import { parseUnits } from 'viem' -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, +const tx = await omsWallet.wallet.sendTransaction({ + network: Networks.amoy, to: '0x1111111111111111111111111111111111111111', value: parseUnits('0.001', 18), waitForStatus: false, }) -const status = await oms.wallet.getTransactionStatus({ txnId: tx.txnId }) +const status = await omsWallet.wallet.getTransactionStatus({ txnId: tx.txnId }) ``` +With `waitForStatus: false`, the response has `statusResolution: 'not-requested'`. + To tune polling, pass `statusPolling`: ```typescript import { parseUnits } from 'viem' -await oms.wallet.sendTransaction({ - network: Networks.polygon, +await omsWallet.wallet.sendTransaction({ + network: Networks.amoy, to: '0x1111111111111111111111111111111111111111', value: parseUnits('0.001', 18), statusPolling: { @@ -428,14 +438,17 @@ await oms.wallet.sendTransaction({ }) ``` -If WaaS returns fee options, pass a selector to choose one. The selector receives -fee options enriched with the current wallet balance for each token when -available. Use `FeeOptionSelector.firstAvailable` to choose the first option the -wallet can pay, or return `option.selection` from a custom selector. +If the wallet API returns fee options, pass a selector to choose one. The +selector receives `FeeOptionWithBalance` values. `balance` is the selected +wallet's raw indexer balance for that fee token when available, `available` is +formatted with the token decimals, `availableRaw` keeps the raw integer value, +and `decimals` is the token decimal count used for formatting. Use +`FeeOptionSelector.firstAvailable` to choose the first option the wallet can +pay, or return `option.selection` from a custom selector. ```typescript -const tx = await oms.wallet.sendTransaction({ - network: Networks.polygon, +const tx = await omsWallet.wallet.sendTransaction({ + network: Networks.amoy, to: '0x3333333333333333333333333333333333333333', data: '0x12345678', selectFeeOption: async (feeOptions) => { @@ -445,11 +458,11 @@ const tx = await oms.wallet.sendTransaction({ }) ``` -## Configuration +## Advanced Configuration ### Publishable-Key Routing -`OMSClient` derives service endpoints from the publishable key. WaaS requests use the API base URL directly; indexer requests use the same base URL with `/v1/IndexerGateway/`. +`OMSWallet` derives service endpoints from the publishable key. Wallet requests use the API base URL directly; indexer requests use the same environment-specific API base. | Publishable key prefix | API base URL | |---|---| @@ -462,32 +475,36 @@ const tx = await oms.wallet.sendTransaction({ ### Custom OIDC Providers +Create a `CustomOidcProviderConfig` and pass it directly to an OIDC redirect +method. Custom configs require `providerRedirectUri`. +They cannot use `omsRelayReturnUri`. + ```typescript -const oms = new OMSClient({ - publishableKey: 'your-publishable-key', - auth: { - oidcProviders: { - custom: { - clientId: 'custom-client-id', - issuer: 'https://issuer.example', - authorizationUrl: 'https://issuer.example/oauth/authorize', - scopes: ['openid', 'email', 'profile'], - }, - }, - }, -}) +import { type CustomOidcProviderConfig } from '@polygonlabs/oms-wallet' + +const acmeProvider = { + clientId: 'acme-client-id', + issuer: 'https://login.acme.example', + authorizationUrl: 'https://login.acme.example/oauth/authorize', + provider: 'acme', + providerLabel: 'Acme', + providerRedirectUri: 'https://app.example/auth/callback', + scopes: ['openid', 'email', 'profile'], +} satisfies CustomOidcProviderConfig + +await omsWallet.wallet.signInWithOidcRedirect({ provider: acmeProvider }) ``` -Provider configs are the source of truth for OIDC scopes. If `scopes` is omitted or empty, the SDK does not send a `scope` authorization parameter. OIDC auth mode defaults to PKCE; pass `authMode` when a provider needs a different WaaS auth-code mode. +Provider configs are the source of truth for OIDC scopes. If `scopes` is omitted or empty, the SDK does not send a `scope` authorization parameter. OIDC auth mode defaults to PKCE; pass `authMode` when a provider needs a different authorization-code mode. ### Custom Storage and Signing -The default storage backend is browser `localStorage` when available, otherwise in-memory storage for wallet metadata only. The default browser signer stores its non-extractable key reference separately through WebCrypto-compatible browser storage. Provide a custom `StorageManager` for persistent Node.js, React Native, or testing sessions: +The default storage backend is browser `localStorage` when available, otherwise in-memory storage for wallet metadata only. The default browser signer stores its non-extractable key reference separately through WebCrypto-compatible browser storage. Provide a custom `StorageManager` when Node.js, tests, or another custom runtime needs persistence: ```typescript -import { MemoryStorageManager, OMSClient } from '@0xsequence/typescript-sdk' +import { MemoryStorageManager, OMSWallet } from '@polygonlabs/oms-wallet' -const oms = new OMSClient({ +const omsWallet = new OMSWallet({ publishableKey: 'your-publishable-key', storage: new MemoryStorageManager(), }) @@ -495,93 +512,163 @@ const oms = new OMSClient({ OIDC redirect auth uses separate transient storage for verifier/state data. In browsers it defaults to `sessionStorage`; pass `redirectAuthStorage` to override it. Final wallet session metadata continues to use the configured `storage`. -## More Examples +## Reference -### Sign and Validate Message +### Networks -```typescript -const signature = await oms.wallet.signMessage({ - network: Networks.polygon, - message: 'some message to sign', -}) +The SDK exports `Networks`, `findNetworkById(id)`, and `findNetworkByName(name)` for the networks currently configured by OMS. Each network has `id`, `name`, `nativeTokenSymbol`, `explorerUrl`, and `displayName`. `name` is the registry/routing slug, while `displayName` is the user-facing label. `Network` is a closed SDK type: use a `Networks` value or a successful lookup result. -const isValid = await oms.wallet.isValidMessageSignature({ - network: Networks.polygon, - walletAddress: oms.wallet.walletAddress, - message: 'some message to sign', - signature, -}) -``` - -### Sign and Validate Typed Data +The `network` parameter on all transaction and signing methods accepts a `Network` from the SDK registry: ```typescript -const signature = await oms.wallet.signTypedData({ - network: Networks.polygon, - typedData, -}) +import { Networks, findNetworkById } from '@polygonlabs/oms-wallet' -const isValid = await oms.wallet.isValidTypedDataSignature({ - network: Networks.polygon, - walletAddress: oms.wallet.walletAddress, - typedData, - signature, -}) +await omsWallet.wallet.signMessage({ network: Networks.amoy, message: 'some message to sign' }) + +console.log(Object.values(Networks)) +console.log(findNetworkById(80002)) // Networks.amoy ``` -### Call a Contract (method string + args) +| Key | id | name | display name | native token | explorerUrl | +|---|---:|---|---|---|---| +| `Networks.mainnet` | 1 | `mainnet` | Ethereum | ETH | `https://etherscan.io` | +| `Networks.sepolia` | 11155111 | `sepolia` | Sepolia | ETH | `https://sepolia.etherscan.io` | +| `Networks.polygon` | 137 | `polygon` | Polygon | POL | `https://polygonscan.com` | +| `Networks.amoy` | 80002 | `amoy` | Polygon Amoy | POL | `https://amoy.polygonscan.com` | +| `Networks.arbitrum` | 42161 | `arbitrum` | Arbitrum | ETH | `https://arbiscan.io` | +| `Networks.arbitrumSepolia` | 421614 | `arbitrum-sepolia` | Arbitrum Sepolia | ETH | `https://sepolia.arbiscan.io` | +| `Networks.optimism` | 10 | `optimism` | Optimism | ETH | `https://optimistic.etherscan.io` | +| `Networks.optimismSepolia` | 11155420 | `optimism-sepolia` | Optimism Sepolia | ETH | `https://sepolia-optimism.etherscan.io` | +| `Networks.base` | 8453 | `base` | Base | ETH | `https://basescan.org` | +| `Networks.baseSepolia` | 84532 | `base-sepolia` | Base Sepolia | ETH | `https://sepolia.basescan.org` | +| `Networks.bsc` | 56 | `bsc` | BSC | BNB | `https://bscscan.com` | +| `Networks.bscTestnet` | 97 | `bsc-testnet` | BSC Testnet | BNB | `https://testnet.bscscan.com` | +| `Networks.arbitrumNova` | 42170 | `arbitrum-nova` | Arbitrum Nova | ETH | `https://nova.arbiscan.io` | +| `Networks.avalanche` | 43114 | `avalanche` | Avalanche | AVAX | `https://subnets.avax.network/c-chain` | +| `Networks.avalancheTestnet` | 43113 | `avalanche-testnet` | Avalanche Testnet | AVAX | `https://subnets-test.avax.network/c-chain` | +| `Networks.katana` | 747474 | `katana` | Katana | ETH | `https://katanascan.com` | -```typescript -import { parseUnits } from 'viem' +### Errors -const tx = await oms.wallet.callContract({ - network: Networks.polygon, - contractAddress: '0x3333333333333333333333333333333333333333', - method: 'transfer(address,uint256)', - args: [ - { type: 'address', value: '0x1111111111111111111111111111111111111111' }, - { type: 'uint256', value: parseUnits('1', 18).toString() }, - ], -}) -``` +Public methods throw `OMSWalletError` subclasses with stable SDK fields such as `code`, `operation`, `status`, and `retryable`. When a failure comes from a remote OMS service response or transport failure, the error also includes `upstreamError` with normalized wallet API or indexer details for logging and service-specific troubleshooting. For `OMSWalletError` values, branch application logic on the SDK-level `code`. -### Query Balances +For transaction writes, `OMS_TRANSACTION_EXECUTION_UNCONFIRMED` means the SDK has a `txnId` from preparation, but the execute request failed before the SDK could confirm whether the transaction was submitted; do not blindly resend the same write. `OMS_TRANSACTION_STATUS_LOOKUP_FAILED` means the transaction was submitted but status polling failed, so retry status lookup with the returned `txnId`. `retryable` describes the failed SDK operation, not the whole user intent. ```typescript -const { walletAddress } = oms.wallet -if (!walletAddress) throw new Error('No active wallet session') - -const result = await oms.indexer.getBalances({ - networks: [Networks.polygon], - walletAddress, - includeMetadata: true, -}) - -for (const b of result.nativeBalances) { - console.log(b.symbol, b.balance) -} +import { OMSWalletError } from '@polygonlabs/oms-wallet' -for (const b of result.balances) { - console.log(b.contractInfo?.symbol, b.balance, b.contractInfo?.decimals) +try { + await omsWallet.wallet.startEmailAuth({ email: 'user@example.com' }) +} catch (err) { + if (err instanceof OMSWalletError) { + console.log(err.code, err.operation, err.upstreamError) + } } ``` -Pass `contractAddresses` to filter balances to specific token contracts. Omit `networks` to query mainnets by default, or pass `networkType: 'TESTNETS'` / `'ALL'`. With `includeMetadata: true`, ERC-20 decimals are available as `contractInfo.decimals`. The response is paginated; pass `page` when requesting later pages. - ### Manage Access ```typescript -const grants = await oms.wallet.listAccess() +const grants = await omsWallet.wallet.listAccess() for (const grant of grants) { console.log(grant.credentialId, grant.expiresAt, grant.isCaller) } -for await (const page of oms.wallet.listAccessPages({ pageSize: 25 })) { +for await (const page of omsWallet.wallet.listAccessPages({ pageSize: 25 })) { console.log('Page:', page.grants) } -await oms.wallet.revokeAccess({ targetCredentialId: grants[0].credentialId }) +await omsWallet.wallet.revokeAccess({ targetCredentialId: grants[0].credentialId }) +``` + +## React Example + +A deployed React example is available at [https://0xsequence.github.io/typescript-sdk/react-example/](https://0xsequence.github.io/typescript-sdk/react-example/). + +To run it locally from the repository root: + +```bash +cp examples/react/.env.example examples/react/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY in examples/react/.env.local +pnpm dev:example +``` + +## Custom Google Redirect Example + +The custom Google redirect example verifies Google configured as a custom OIDC +provider with `providerRedirectUri: "http://localhost:5173"`. It does not use +the SDK built-in Google relay helper. + +To run it locally from the repository root: + +```bash +cp examples/custom-google-redirect/.env.example examples/custom-google-redirect/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY in examples/custom-google-redirect/.env.local +pnpm dev:custom-google-redirect-example +``` + +## Wagmi Connector + +This workspace also includes `@polygonlabs/oms-wallet-wagmi-connector`, an ESM-only package that adapts an +active OMS Wallet SDK instance to wagmi's connector API. + +```bash +pnpm --filter @polygonlabs/oms-wallet-wagmi-connector build +pnpm --filter @polygonlabs/oms-wallet-wagmi-connector test +``` + +See [packages/oms-wallet-wagmi-connector/README.md](./packages/oms-wallet-wagmi-connector/README.md) for usage. + +## Wagmi React Example + +The Wagmi example uses `@polygonlabs/oms-wallet-wagmi-connector`, wagmi's MetaMask connector, and the Trails widget. + +The deployed Wagmi example is available at [https://0xsequence.github.io/typescript-sdk/wagmi-example/](https://0xsequence.github.io/typescript-sdk/wagmi-example/). + +To run it locally from the repository root: + +```bash +cp examples/wagmi/.env.example examples/wagmi/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY in examples/wagmi/.env.local +# Replace VITE_TRAILS_API_KEY only if you need a different Trails project +pnpm dev:wagmi-example +``` + +## Trails Actions React Example + +The Trails Actions example prepares and sends Polygon swap, Earn deposit, swap plus Earn deposit, and Earn withdrawal flows with `0xtrails/actions`. + +The deployed Trails Actions example is available at [https://0xsequence.github.io/typescript-sdk/trails-actions-example/](https://0xsequence.github.io/typescript-sdk/trails-actions-example/). + +To run it locally from the repository root: + +```bash +cp examples/trails-actions/.env.example examples/trails-actions/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY in examples/trails-actions/.env.local +pnpm dev:trails-actions-example +``` + +## Node Example + +The Node example walks through email OTP sign-in and message signing from a terminal. + +To run it locally from the repository root: + +```bash +OMS_PUBLISHABLE_KEY=your-publishable-key pnpm dev:node-example +``` + +## Node Contract Deploy Example + +The Node contract deploy example compiles a small ERC-20 contract and submits a Polygon Amoy deployment transaction through the OMS Wallet API. + +To run it locally from the repository root: + +```bash +cp examples/node-contract-deploy-example/.env.example examples/node-contract-deploy-example/.env.local +# Fill OMS_PUBLISHABLE_KEY in examples/node-contract-deploy-example/.env.local +pnpm dev:node-contract-deploy-example ``` ## API Reference @@ -601,3 +688,7 @@ See [PUBLISHING.md](./PUBLISHING.md) for release and npm publishing steps. 5. **Open a PR** — the PR template will walk you through the checklist. See [`TESTING.md`](./TESTING.md) for full testing conventions and commands. + +## License + +Apache-2.0. See [LICENSE](./LICENSE). diff --git a/TESTING.md b/TESTING.md index 38f41fa..647a98d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -6,6 +6,7 @@ How testing works in this repo. `AGENTS.md` points here so agents know how to ve - **Test runner:** [Vitest](https://vitest.dev/) v4+ - **Type tests:** `tsc --noEmit` (compile-time API assertions in `type-tests/`) +- **Packaged API check:** TypeScript AST comparison of built declarations with a committed public API baseline - **No coverage enforcement** currently — focus is on behavioral correctness - **Environment:** `dotenv` loaded via `vitest.config.ts`; tests run serially (`fileParallelism: false`) @@ -17,7 +18,7 @@ How testing works in this repo. `AGENTS.md` points here so agents know how to ve - **Location:** `tests/**/*.ts` - **Run:** `pnpm exec vitest run` (or `pnpm test` which runs this then type tests) - **Package tests:** `packages/oms-wallet-wagmi-connector/tests/**/*.ts` run from that package with - `pnpm --filter @0xsequence/oms-wallet-wagmi-connector test` + `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector test` ## Integration / type tests @@ -25,23 +26,25 @@ How testing works in this repo. `AGENTS.md` points here so agents know how to ve expected shapes. These catch public API regressions that runtime tests cannot. - **Location:** `type-tests/oidcProviderTypes.ts` - **Run:** `pnpm test:types` -- **Note:** A few tests in `tests/` seed private wallet state via `(wallet as any)` for legacy - compatibility. Use public methods or small fixtures for any new test coverage. +- **Note:** A few existing tests in `tests/` seed private wallet state via `(wallet as any)` to + exercise established session fixtures. Use public methods or small fixtures for new coverage. ## When to run what | Scenario | Command | |---|---| | Changed publishable package versions | `pnpm check:package-versions` | +| Preparing a stable release | `pnpm check:stable-package-versions` | | Changed SDK behavior | `pnpm exec vitest run` | -| Changed wagmi connector behavior | `pnpm --filter @0xsequence/oms-wallet-wagmi-connector test` | -| Changed wagmi connector types/build | `pnpm --filter @0xsequence/oms-wallet-wagmi-connector build` | +| Changed wagmi connector behavior | `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector test` | +| Changed wagmi connector types/build | `pnpm --filter @polygonlabs/oms-wallet-wagmi-connector build` | | Changed React example | `pnpm build:example` | | Changed Trails Actions example | `pnpm build:trails-actions-example` | | Changed wagmi React example | `pnpm build:wagmi-example` | | Changed Node example | `pnpm build:node-example` | | Changed Node contract deploy example | `pnpm build:node-contract-deploy-example` | | Changed public types / `src/index.ts` | `pnpm test:types` | +| Changed built declarations or package exports | `pnpm build && pnpm check:public-api` | | Full pre-handoff check | `pnpm exec tsc --noEmit && pnpm test` | | Watch mode during development | `pnpm test:watch` | | High-risk paths (auth, signing, tx, storage) | Add a focused regression test, then `pnpm test` | @@ -57,9 +60,9 @@ How testing works in this repo. `AGENTS.md` points here so agents know how to ve - Use `docs/error-contracts.md` as the audit matrix for public SDK/connector error surfaces, recovery semantics, `upstreamError` expectations, and owning tests. -- Exercise real public runtime APIs such as `oms.wallet.*`, `oms.indexer.*`, exported storage +- Exercise real public runtime APIs such as `omsWallet.wallet.*`, `omsWallet.indexer.*`, exported storage managers, signers, or wagmi connector/provider methods. -- Do not snapshot manually constructed `OmsSdkError` subclasses unless the error class or helper +- Do not snapshot manually constructed `OMSWalletError` subclasses unless the error class or helper is the unit under test. - Mock only external boundaries: `fetch`, browser globals, storage availability, signer behavior, timers, or backend responses. @@ -89,4 +92,5 @@ How testing works in this repo. `AGENTS.md` points here so agents know how to ve | Run type tests | `pnpm test:types` | | Run everything | `pnpm test` | | Typecheck (no emit) | `pnpm exec tsc --noEmit` | +| Check packaged declarations | `pnpm check:public-api` | | Watch mode | `pnpm test:watch` | diff --git a/docs/error-contracts.md b/docs/error-contracts.md index 3b87471..7934266 100644 --- a/docs/error-contracts.md +++ b/docs/error-contracts.md @@ -21,38 +21,38 @@ supports, whether `upstreamError` should be present, and which test owns the con | Public surface | Failure family | User-facing error | Recovery meaning | `upstreamError` | Covering test | |---|---|---|---|---|---| -| `oms.wallet.startEmailAuth`, representative WaaS methods | WaaS transport failure | `OmsRequestError`, `OMS_REQUEST_FAILED`, operation-specific, retryable when transport/5xx | Retry the same read/auth request when appropriate | Present | `snapshots WaaS transport failures with upstream details` | -| `oms.wallet.startEmailAuth`, representative WaaS methods | WaaS domain error | SDK-specific code such as `OMS_AUTH_COMMITMENT_CONSUMED` | Follow the SDK code; for consumed commitments, restart auth | Present | `snapshots WaaS domain errors with upstream details` | -| `oms.wallet.startEmailAuth`, representative WaaS methods | WaaS HTTP error | `OmsRequestError`, `OMS_HTTP_ERROR`, status, retryable for 5xx | Use SDK code/status for branching; log upstream detail | Present | `snapshots WaaS HTTP responses with upstream details` | -| `oms.wallet.completeEmailAuth` and pending wallet selection actions | Local auth/session/selection state | `OmsSessionError` or `OmsWalletSelectionError` | Fix local flow state or restart auth; do not look for backend diagnostics | Absent | `snapshots email auth completion local state errors`; `snapshots pending wallet selection local state errors` | -| `oms.wallet.startOidcRedirectAuth`, `completeOidcRedirectAuth`, `signInWithOidcRedirect` | Local OIDC config, callback, storage, or state mismatch | `OmsSessionError` or wrapped SDK-local error | Fix redirect config/state or restart OIDC flow | Absent | `snapshots OIDC local error contracts without upstream details`; `snapshots OIDC redirect real-flow local mismatch errors`; `snapshots signInWithOidcRedirect missing assignUrl after real redirect start` | -| Protected wallet methods: `listWallets`, `useWallet`, `createWallet`, `getIdToken`, `signMessage`, `signTypedData`, `sendTransaction`, `callContract`, `listAccess`, `listAccessPages`, `revokeAccess` | Missing, expired, or stale local session | `OmsSessionError` | Authenticate again or recover local session; no remote request was made | Absent | `snapshots missing-session contracts for protected wallet methods` | -| `oms.wallet.sendTransaction`, `callContract` | SDK-local transaction validation or fee-selection failure | `OmsValidationError` | Correct parameters or local fee selection; do not retry as an upstream outage | Absent | `snapshots transaction local validation errors without upstream details` | -| `oms.wallet.isValidMessageSignature`, `isValidTypedDataSignature` | WaaS validation backend failure | `OmsRequestError` or `OmsResponseError` with validation operation | Retry based on SDK code/status; log upstream detail | Present | `snapshots signature validation backend failures with upstream details` | -| `oms.wallet.sendTransaction`, `callContract` | Execute request fails after prepare | `OmsTransactionError`, `OMS_TRANSACTION_EXECUTION_UNCONFIRMED`, `retryable: false`, `txnId` when available | Do not blindly resend the write; preserve `txnId` and upstream detail for diagnostics | Present when execute crossed transport/upstream boundary | `snapshots transaction execute failures as unconfirmed writes` | -| `oms.wallet.sendTransaction`, `callContract` | Submitted transaction status polling fails | `OmsTransactionError`, `OMS_TRANSACTION_STATUS_LOOKUP_FAILED`, `retryable: true`, `txnId` | Retry status lookup, not the original write | Present when polling crossed transport/upstream boundary | `snapshots transaction status polling failures with txn and upstream details`; `snapshots transaction status polling backend errors as retryable` | -| `oms.wallet.getTransactionStatus` | Direct status lookup backend failure | `OmsRequestError` or `OmsResponseError` with status operation | Retry status lookup or surface backend status to the user | Present | `snapshots direct transaction status backend errors with upstream details` | -| `oms.wallet.listAccess`, `listAccessPages`, `revokeAccess` | WaaS access backend failure | `OmsRequestError` or `OmsResponseError` with access operation | Retry based on SDK code/status; log upstream detail | Present | `snapshots access backend errors with upstream details` | -| `oms.indexer.getBalances`, `getTransactionHistory` | Indexer backend, transport, malformed JSON, or malformed payload | `OmsRequestError` or `OmsResponseError` with indexer operation | Retry based on SDK code/status; log upstream detail | Present for remote/transport response failures | `snapshots indexer backend errors with upstream details`; `snapshots transaction history indexer errors with upstream details`; `snapshots indexer transport failures with upstream details`; `snapshots indexer malformed response errors with upstream details` | -| `oms.indexer.getBalances`, `getTransactionHistory` | Indexer non-JSON HTTP body | `OmsRequestError`, `OMS_HTTP_ERROR`, sanitized message | Do not expose raw upstream HTML/text bodies; log normalized detail | Present, sanitized | `snapshots indexer non-JSON HTTP errors without raw upstream bodies` | +| `omsWallet.wallet.startEmailAuth`, representative WaaS methods | WaaS transport failure | `OMSWalletRequestError`, `OMS_REQUEST_FAILED`, operation-specific, retryable when transport/5xx | Retry the same read/auth request when appropriate | Present | `snapshots WaaS transport failures with upstream details` | +| `omsWallet.wallet.startEmailAuth`, representative WaaS methods | WaaS domain error | SDK-specific code such as `OMS_AUTH_COMMITMENT_CONSUMED` | Follow the SDK code; for consumed commitments, restart auth | Present | `snapshots WaaS domain errors with upstream details` | +| `omsWallet.wallet.startEmailAuth`, representative WaaS methods | WaaS HTTP error | `OMSWalletRequestError`, `OMS_HTTP_ERROR`, status, retryable for 5xx | Use SDK code/status for branching; log upstream detail | Present | `snapshots WaaS HTTP responses with upstream details` | +| `omsWallet.wallet.completeEmailAuth` and pending wallet selection actions | Local auth/session/selection state | `OMSWalletSessionError` or `OMSWalletSelectionError` | Fix local flow state or restart auth; do not look for backend diagnostics | Absent | `snapshots email auth completion local state errors`; `snapshots pending wallet selection local state errors` | +| `omsWallet.wallet.startOidcRedirectAuth`, `completeOidcRedirectAuth`, `signInWithOidcRedirect` | Local OIDC config, callback, storage, or state mismatch | `OMSWalletSessionError` or wrapped SDK-local error | Fix redirect config/state or restart OIDC flow | Absent | `snapshots OIDC local error contracts without upstream details`; `snapshots OIDC redirect real-flow local mismatch errors`; `snapshots signInWithOidcRedirect missing assignUrl after real redirect start` | +| Protected wallet methods: `listWallets`, `useWallet`, `createWallet`, `getIdToken`, `signMessage`, `signTypedData`, `sendTransaction`, `callContract`, `listAccess`, `listAccessPages`, `revokeAccess` | Missing, expired, or stale local session | `OMSWalletSessionError` | Authenticate again or recover local session; no remote request was made | Absent | `snapshots missing-session contracts for protected wallet methods` | +| `omsWallet.wallet.sendTransaction`, `callContract` | SDK-local transaction validation or fee-selection failure | `OMSWalletValidationError` | Correct parameters or local fee selection; do not retry as an upstream outage | Absent | `snapshots transaction local validation errors without upstream details` | +| `omsWallet.wallet.isValidMessageSignature`, `isValidTypedDataSignature` | WaaS validation backend failure | `OMSWalletRequestError` or `OMSWalletResponseError` with validation operation | Retry based on SDK code/status; log upstream detail | Present | `snapshots signature validation backend failures with upstream details` | +| `omsWallet.wallet.sendTransaction`, `callContract` | Execute request fails after prepare | `OMSWalletTransactionError`, `OMS_TRANSACTION_EXECUTION_UNCONFIRMED`, `retryable: false`, `txnId` when available | Do not blindly resend the write; preserve `txnId` and upstream detail for diagnostics | Present when execute crossed transport/upstream boundary | `snapshots transaction execute failures as unconfirmed writes` | +| `omsWallet.wallet.sendTransaction`, `callContract` | Submitted transaction status polling fails | `OMSWalletTransactionError`, `OMS_TRANSACTION_STATUS_LOOKUP_FAILED`, `retryable: true`, `txnId` | Retry status lookup, not the original write | Present when polling crossed transport/upstream boundary | `snapshots transaction status polling failures with txn and upstream details`; `snapshots transaction status polling backend errors as retryable` | +| `omsWallet.wallet.getTransactionStatus` | Direct status lookup backend failure | `OMSWalletRequestError` or `OMSWalletResponseError` with status operation | Retry status lookup or surface backend status to the user | Present | `snapshots direct transaction status backend errors with upstream details` | +| `omsWallet.wallet.listAccess`, `listAccessPages`, `revokeAccess` | WaaS access backend failure | `OMSWalletRequestError` or `OMSWalletResponseError` with access operation | Retry based on SDK code/status; log upstream detail | Present | `snapshots access backend errors with upstream details` | +| `omsWallet.indexer.getBalances`, `getTransactionHistory` | Indexer backend, transport, malformed JSON, or malformed payload | `OMSWalletRequestError` or `OMSWalletResponseError` with indexer operation | Retry based on SDK code/status; log upstream detail | Present for remote/transport response failures | `snapshots indexer backend errors with upstream details`; `snapshots transaction history indexer errors with upstream details`; `snapshots indexer transport failures with upstream details`; `snapshots indexer malformed response errors with upstream details` | +| `omsWallet.indexer.getBalances`, `getTransactionHistory` | Indexer non-JSON HTTP body | `OMSWalletRequestError`, `OMS_HTTP_ERROR`, sanitized message | Do not expose raw upstream HTML/text bodies; log normalized detail | Present, sanitized | `snapshots indexer non-JSON HTTP errors without raw upstream bodies` | | Exported storage managers and credential signers | Local runtime capability or storage failure | Native `Error` or signer/storage-specific runtime error | Fix local runtime support or storage availability | Absent | `snapshots exported storage and signer runtime errors` | -| Exported `OmsSdkError` classes and `isOmsSdkError` | Error class/helper field contract | Stable public fields on constructed errors | Use only when the error class/helper is the unit under test | As constructed | `snapshots exported error helper and subclass fields` | +| Exported `OMSWalletError` classes and `isOMSWalletError` | Error class/helper field contract | Stable public fields on constructed errors | Use only when the error class/helper is the unit under test | As constructed | `snapshots exported error helper and subclass fields` | ## Connector Matrix | Public surface | Failure family | User-facing error | Recovery meaning | `upstreamError` | Covering test | |---|---|---|---|---|---| -| `omsWalletConnector().connect` | No active OMS wallet session | `OmsWalletProviderRpcError`, `4100` through wagmi connect wrapping | Authenticate with the OMS SDK before connecting wagmi | Not applicable | `rejects connect when there is no active OMS wallet session` | +| `omsWalletConnector().connect` | No active OMS wallet session | `OMSWalletProviderRpcError`, `4100` through wagmi connect wrapping | Authenticate with the OMS Wallet SDK before connecting wagmi | Not applicable | `rejects connect when there is no active OMS wallet session` | | `omsWalletConnector().connect` | Initial chain not configured in wagmi | Provider/connector chain error | Configure the chain in wagmi or choose a configured chain | Not applicable | `rejects connect when initialChainId is not configured in wagmi` | | `omsWalletConnector().connect` | No configured wagmi chain is supported by OMS | Provider/connector chain error | Configure at least one chain supported by OMS | Not applicable | `rejects connect when no configured wagmi chain is supported by OMS` | | `connector.getAccounts`, `isAuthorized`, `disconnect`, `getProvider` | Disconnected connector state | Wagmi connector state errors or non-throwing state results | Reconnect through wagmi when needed | Not applicable | `disconnects wagmi without signing out the OMS wallet`; `does not reconnect automatically after disconnecting and refreshing`; `keeps session expiry disconnect handling after reconnect` | -| `OmsWalletProvider.request({method: "eth_requestAccounts"})` | No active OMS wallet session | `OmsWalletProviderRpcError`, `4100` | Authenticate with the OMS SDK before requesting accounts | Not applicable | `rejects eth_requestAccounts without an active OMS wallet session` | -| `OmsWalletProvider.request`, unsupported methods | Unsupported raw signing or unsupported RPC method | `OmsWalletProviderRpcError`, `4200` | Use supported methods only | Not applicable | `rejects eth_sign because OMS Wallet does not raw-sign messages`; `rejects legacy typed data signing instead of treating it as v4` | -| `personal_sign`, `eth_signTypedData_v4` | Provider parameter validation or account mismatch | `OmsWalletProviderRpcError`, `-32602` or `4100` | Correct params or use the active OMS account; no wallet SDK call should be made for malformed inputs | Not applicable | `rejects provider signing validation errors before calling OMS` | -| `eth_sendTransaction`, `wallet_sendTransaction` | Provider transaction parameter validation | `OmsWalletProviderRpcError`, `-32602`, `4100`, or `4200` | Correct params; no wallet SDK transaction should be sent for malformed inputs | Not applicable | `rejects provider transaction validation errors before calling OMS`; `rejects unknown transaction fields`; `rejects non-quantity transaction values at the provider boundary`; `rejects waitForStatus false because wagmi sendTransaction requires a hash` | -| `eth_sendTransaction`, `wallet_sendTransaction` | Chain configured by wagmi but unsupported by OMS | `OmsWalletProviderRpcError`, `4901` | Choose an OMS-supported chain; no wallet SDK transaction should be sent | Not applicable | `rejects transactions for wagmi-configured chains that OMS does not support` | -| `eth_sendTransaction`, `wallet_sendTransaction` | SDK transaction failure | `OmsWalletProviderRpcError`, `-32603`, SDK error preserved in `data` | Preserve SDK recovery fields through provider and wagmi wrapping | SDK error may carry `upstreamError` in `data` | `wraps SDK transaction failures as provider RPC errors`; `preserves SDK transaction error details through wagmi sendTransaction wrapping` | -| `eth_sendTransaction`, `wallet_sendTransaction` | OMS response lacks EVM transaction hash | `OmsWalletProviderRpcError`, `-32603`, response preserved | Surface the OMS transaction id when available; wagmi requires an EVM hash | Not applicable unless response data carries it | `rejects with the OMS response when a sent transaction has no EVM hash` | -| `wallet_switchEthereumChain`, `switchChain` | Chain not configured in wagmi or unsupported by OMS | `OmsWalletProviderRpcError` or wagmi switch error, `4901` | Configure the chain and ensure OMS supports it | Not applicable | `rejects provider chain switches to OMS-supported chains that are not configured in wagmi`; `rejects provider chain switches to wagmi-configured chains that OMS does not support`; `rejects wagmi switchChain calls to wagmi-configured chains that OMS does not support`; `uses and validates initialChainId`; `rejects initialChainId when OMS does not support it` | -| Exported `OmsWalletProviderRpcError` | Provider error class field contract | Stable public fields: `name`, `code`, `message`, `data` | Branch on EIP-1193 `code`; use `data` for preserved SDK diagnostics | Not applicable | `preserves the exported provider RPC error field contract` | +| `OMSWalletProvider.request({method: "eth_requestAccounts"})` | No active OMS wallet session | `OMSWalletProviderRpcError`, `4100` | Authenticate with the OMS Wallet SDK before requesting accounts | Not applicable | `rejects eth_requestAccounts without an active OMS wallet session` | +| `OMSWalletProvider.request`, unsupported methods | Unsupported raw signing or unsupported RPC method | `OMSWalletProviderRpcError`, `4200` | Use supported methods only | Not applicable | `rejects eth_sign because OMS Wallet does not raw-sign messages`; `rejects legacy typed data signing instead of treating it as v4` | +| `personal_sign`, `eth_signTypedData_v4` | Provider parameter validation or account mismatch | `OMSWalletProviderRpcError`, `-32602` or `4100` | Correct params or use the active OMS account; no wallet SDK call should be made for malformed inputs | Not applicable | `rejects provider signing validation errors before calling OMS` | +| `eth_sendTransaction`, `wallet_sendTransaction` | Provider transaction parameter validation | `OMSWalletProviderRpcError`, `-32602`, `4100`, or `4200` | Correct params; no wallet SDK transaction should be sent for malformed inputs | Not applicable | `rejects provider transaction validation errors before calling OMS`; `rejects unknown transaction fields`; `rejects non-quantity transaction values at the provider boundary`; `rejects waitForStatus false because wagmi sendTransaction requires a hash` | +| `eth_sendTransaction`, `wallet_sendTransaction` | Chain configured by wagmi but unsupported by OMS | `OMSWalletProviderRpcError`, `4901` | Choose an OMS-supported chain; no wallet SDK transaction should be sent | Not applicable | `rejects transactions for wagmi-configured chains that OMS does not support` | +| `eth_sendTransaction`, `wallet_sendTransaction` | SDK transaction failure | `OMSWalletProviderRpcError`, `-32603`, SDK error preserved in `data` | Preserve SDK recovery fields through provider and wagmi wrapping | SDK error may carry `upstreamError` in `data` | `wraps SDK transaction failures as provider RPC errors`; `preserves SDK transaction error details through wagmi sendTransaction wrapping` | +| `eth_sendTransaction`, `wallet_sendTransaction` | OMS response lacks EVM transaction hash | `OMSWalletProviderRpcError`, `-32603`, response preserved | Surface the OMS transaction id when available; wagmi requires an EVM hash | Not applicable unless response data carries it | `rejects with the OMS response when a sent transaction has no EVM hash` | +| `wallet_switchEthereumChain`, `switchChain` | Chain not configured in wagmi or unsupported by OMS | `OMSWalletProviderRpcError` or wagmi switch error, `4901` | Configure the chain and ensure OMS supports it | Not applicable | `rejects provider chain switches to OMS-supported chains that are not configured in wagmi`; `rejects provider chain switches to wagmi-configured chains that OMS does not support`; `rejects wagmi switchChain calls to wagmi-configured chains that OMS does not support`; `uses and validates initialChainId`; `rejects initialChainId when OMS does not support it` | +| Exported `OMSWalletProviderRpcError` | Provider error class field contract | Stable public fields: `name`, `code`, `message`, `data` | Branch on EIP-1193 `code`; use `data` for preserved SDK diagnostics | Not applicable | `preserves the exported provider RPC error field contract` | | `eth_chainId`, `net_version`, `eth_accounts`, `wallet_getCapabilities`, `stringToPersonalSignHex` | Non-throwing public utility/state calls | Return current state or converted value | No error contract expected unless implementation changes | Not applicable | Covered indirectly by behavior tests or intentionally skipped | diff --git a/examples/custom-google-redirect/.env.example b/examples/custom-google-redirect/.env.example new file mode 100644 index 0000000..a92c198 --- /dev/null +++ b/examples/custom-google-redirect/.env.example @@ -0,0 +1 @@ +VITE_OMS_PUBLISHABLE_KEY=your-publishable-key diff --git a/examples/custom-google-redirect/README.md b/examples/custom-google-redirect/README.md new file mode 100644 index 0000000..159f41f --- /dev/null +++ b/examples/custom-google-redirect/README.md @@ -0,0 +1,17 @@ +# Custom Google Redirect Example + +This Vite React example verifies Google as a custom OIDC provider. It configures +Google with `providerRedirectUri: "http://localhost:5173"` and does not use the +SDK built-in Google relay value. The example passes its direct +`CustomOidcProviderConfig` to `signInWithOidcRedirect`. + +## Run Locally + +```bash +cp examples/custom-google-redirect/.env.example examples/custom-google-redirect/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY in examples/custom-google-redirect/.env.local +pnpm dev:custom-google-redirect-example +``` + +The Google OAuth client in this example is configured for +`http://localhost:5173`, so run the app on that exact origin. diff --git a/examples/custom-google-redirect/index.html b/examples/custom-google-redirect/index.html new file mode 100644 index 0000000..6bd0ffe --- /dev/null +++ b/examples/custom-google-redirect/index.html @@ -0,0 +1,18 @@ + + + + + + + + + OMS Wallet Custom Google Redirect Example + + +
+ + + diff --git a/examples/custom-google-redirect/package.json b/examples/custom-google-redirect/package.json new file mode 100644 index 0000000..7fe6a08 --- /dev/null +++ b/examples/custom-google-redirect/package.json @@ -0,0 +1,23 @@ +{ + "name": "custom-google-redirect-example", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@polygonlabs/oms-wallet": "workspace:*", + "react": "19.2.7", + "react-dom": "19.2.7" + }, + "devDependencies": { + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "typescript": "^5.9.3", + "vite": "^8.0.10" + } +} diff --git a/examples/custom-google-redirect/src/config.ts b/examples/custom-google-redirect/src/config.ts new file mode 100644 index 0000000..0b469a7 --- /dev/null +++ b/examples/custom-google-redirect/src/config.ts @@ -0,0 +1,20 @@ +export const PUBLISHABLE_KEY = requiredEnv( + 'VITE_OMS_PUBLISHABLE_KEY', + import.meta.env.VITE_OMS_PUBLISHABLE_KEY, +) + +export const CUSTOM_GOOGLE_CLIENT_ID = + '970987756660-0dh5gubqfiugm452raf7mm39qaq639hn.apps.googleusercontent.com' + +export const CUSTOM_GOOGLE_ISSUER = 'https://accounts.google.com' + +export const CUSTOM_GOOGLE_REDIRECT_URI = 'http://localhost:5173' + +function requiredEnv(name: string, value: string | undefined): string { + if (!value) { + throw new Error( + `Missing ${name}. Copy examples/custom-google-redirect/.env.example to examples/custom-google-redirect/.env.local and set it.`, + ) + } + return value +} diff --git a/examples/custom-google-redirect/src/main.tsx b/examples/custom-google-redirect/src/main.tsx new file mode 100644 index 0000000..504e812 --- /dev/null +++ b/examples/custom-google-redirect/src/main.tsx @@ -0,0 +1,245 @@ +import { useEffect, useRef, useState } from 'react' +import { createRoot } from 'react-dom/client' +import { Networks, type TokenBalance } from '@polygonlabs/oms-wallet' +import './styles.css' +import { formatSessionAuth, formatSessionExpiry, hasOidcCallbackParams, isPendingWalletSelection } from '../../shared/example-utils' +import { CUSTOM_GOOGLE_CLIENT_ID, CUSTOM_GOOGLE_ISSUER, CUSTOM_GOOGLE_REDIRECT_URI } from './config' +import { customGoogleOidcProvider, omsWallet } from './omsWallet' + +const DEFAULT_MESSAGE = 'hello from OMS Wallet' +const BALANCE_NETWORKS = [Networks.polygon, Networks.base, Networks.arbitrum] + +function App() { + const restoredWalletAddress = omsWallet.wallet.walletAddress ?? '' + const [walletAddress, setWalletAddress] = useState(restoredWalletAddress) + const [status, setStatus] = useState( + restoredWalletAddress + ? 'Wallet session restored.' + : 'Ready to sign in with the custom Google provider.', + ) + const [message, setMessage] = useState(DEFAULT_MESSAGE) + const [lastSignature, setLastSignature] = useState('') + const [lastIdToken, setLastIdToken] = useState('') + const [balances, setBalances] = useState(null) + const [isBusy, setIsBusy] = useState(false) + const callbackStarted = useRef(false) + + useEffect(() => { + if (!hasOidcCallbackParams()) return + if (callbackStarted.current) return + callbackStarted.current = true + void completeRedirect() + }, []) + + async function run(label: string, action: () => Promise) { + setIsBusy(true) + setStatus(label) + try { + await action() + } catch (error) { + setStatus(error instanceof Error ? error.message : String(error)) + } finally { + setIsBusy(false) + } + } + + async function startRedirect() { + await run('Opening Google sign-in...', async () => { + await omsWallet.wallet.signInWithOidcRedirect({ + provider: customGoogleOidcProvider, + }) + }) + } + + async function completeRedirect() { + await run('Finishing Google sign-in...', async () => { + const result = await omsWallet.wallet.completeOidcRedirectAuth() + + if (!result) { + setStatus('No matching Google callback found for this session.') + return + } + + if (isPendingWalletSelection(result)) { + setStatus('Choose automatic wallet selection for this example.') + return + } + + setWalletAddress(result.walletAddress) + setStatus('Google sign-in complete.') + }) + } + + async function signMessage() { + await run('Signing message...', async () => { + const signature = await omsWallet.wallet.signMessage({ + network: Networks.amoy, + message, + }) + setLastSignature(signature) + setStatus('Message signed.') + }) + } + + async function loadBalances() { + if (!walletAddress) return + + await run('Loading balances...', async () => { + const result = await omsWallet.indexer.getBalances({ + walletAddress, + networks: BALANCE_NETWORKS, + includeMetadata: true, + }) + setBalances([...result.nativeBalances, ...result.balances]) + setStatus(`Loaded ${result.nativeBalances.length + result.balances.length} balances.`) + }) + } + + async function getIdToken() { + await run('Getting ID token...', async () => { + const idToken = await omsWallet.wallet.getIdToken() + setLastIdToken(idToken) + setStatus('ID token issued.') + }) + } + + async function signOut() { + await run('Signing out...', async () => { + await omsWallet.wallet.signOut() + setWalletAddress('') + setLastSignature('') + setLastIdToken('') + setBalances(null) + setStatus('Signed out. You can start again.') + }) + } + + return ( +
+
+
+

OMS Wallet example

+

Custom Google sign-in

+
+ +
+
+ Google OAuth client + {CUSTOM_GOOGLE_CLIENT_ID} +
+
+ Redirect URI + {CUSTOM_GOOGLE_REDIRECT_URI} +
+
+ Issuer + {CUSTOM_GOOGLE_ISSUER} +
+
+ + {status} + + {!walletAddress ? ( + + ) : null} + + {walletAddress ? ( +
+

Active session

+
+
+
Wallet address
+
{walletAddress || 'Unknown'}
+
+
+
Sign-in method
+
{formatSessionAuth(omsWallet.wallet.session.auth)}
+
+
+
Email
+
{omsWallet.wallet.session.auth?.email ?? 'Unknown'}
+
+
+
Session expires
+
{formatSessionExpiry(omsWallet.wallet.session.expiresAt)}
+
+
+ +
+

Sign message

+ + + {lastSignature ? ( +

+ Signature + {lastSignature} +

+ ) : null} +
+ +
+
+

Read balances

+ Polygon, Base, Arbitrum +
+ + {balances && balances.length > 0 ? ( +
+ {balances.map((balance, index) => ( +
+ + {balance.contractAddress === undefined + ? balance.symbol + : (balance.contractInfo?.symbol ?? 'Token')} + + {balance.balance} +
+ ))} +
+ ) : ( +

+ {balances ? 'No balances found for the active wallet.' : 'Load balances for the active wallet.'} +

+ )} +
+ +
+

ID token

+ + {lastIdToken ? ( +

+ ID token + {lastIdToken} +

+ ) : null} +
+ + +
+ ) : null} +
+
+ ) +} + +createRoot(document.getElementById('root')!).render() diff --git a/examples/custom-google-redirect/src/omsWallet.ts b/examples/custom-google-redirect/src/omsWallet.ts new file mode 100644 index 0000000..77dcd99 --- /dev/null +++ b/examples/custom-google-redirect/src/omsWallet.ts @@ -0,0 +1,16 @@ +import { OMSWallet, type CustomOidcProviderConfig } from '@polygonlabs/oms-wallet' +import { CUSTOM_GOOGLE_CLIENT_ID, CUSTOM_GOOGLE_ISSUER, CUSTOM_GOOGLE_REDIRECT_URI, PUBLISHABLE_KEY } from './config' + +export const customGoogleOidcProvider = { + clientId: CUSTOM_GOOGLE_CLIENT_ID, + issuer: CUSTOM_GOOGLE_ISSUER, + authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth', + provider: 'google', + providerLabel: 'Google', + scopes: ['openid', 'email', 'profile'], + providerRedirectUri: CUSTOM_GOOGLE_REDIRECT_URI, +} satisfies CustomOidcProviderConfig + +export const omsWallet = new OMSWallet({ + publishableKey: PUBLISHABLE_KEY, +}) diff --git a/examples/custom-google-redirect/src/styles.css b/examples/custom-google-redirect/src/styles.css new file mode 100644 index 0000000..c82e9b9 --- /dev/null +++ b/examples/custom-google-redirect/src/styles.css @@ -0,0 +1,127 @@ +@import url("../../shared/oms-example-base.css"); + +.provider-metadata { + display: grid; + gap: 8px; + padding: 12px; + border: 1px solid var(--oms-slate-200); + border-radius: 12px; + background: var(--oms-slate-50); +} + +.provider-metadata-row { + display: grid; + gap: 4px; + min-width: 0; +} + +.provider-metadata-row span { + color: var(--oms-muted-ink); + font-size: 12px; + font-weight: 700; +} + +.provider-metadata-row code { + display: block; + min-width: 0; + padding: 8px 10px; + border-radius: var(--oms-radius-button); + color: var(--oms-slate-800); + background: var(--oms-surface); + overflow-wrap: anywhere; +} + +.session-details { + display: grid; + gap: 8px; + margin: 0; +} + +.session-details div { + display: grid; + gap: 4px; + min-width: 0; +} + +.session-details dt, +.session-details dd { + min-width: 0; + overflow-wrap: anywhere; +} + +.session-details dt { + color: var(--oms-muted-ink); + font-size: 12px; + font-weight: 700; +} + +.session-details dd { + margin: 0; + color: var(--oms-ink); + font-size: 14px; + font-weight: 700; +} + +.result { + display: grid; + gap: 4px; + min-width: 0; + max-height: 120px; + overflow: auto; + overflow-wrap: anywhere; + margin: 0; + padding: 10px 12px; + border-radius: 12px; + background: var(--oms-slate-950); + color: var(--oms-purple-100); + font-family: var(--oms-font-mono); +} + +.result-label { + color: var(--oms-slate-400); + font-size: 12px; + font-weight: 800; +} + +.result-value { + color: var(--oms-purple-300); +} + +.balance-list { + display: grid; + gap: 8px; +} + +.balance-row { + display: grid; + grid-template-columns: minmax(0, 140px) minmax(0, 1fr); + gap: 10px; + min-width: 0; + padding: 10px 12px; + border: 1px solid var(--oms-slate-200); + border-radius: 12px; + background: var(--oms-surface); +} + +.balance-row span { + min-width: 0; + color: var(--oms-ink); + font-size: 14px; + font-weight: 700; + overflow-wrap: anywhere; +} + +.balance-row code { + min-width: 0; + text-align: right; +} + +@media (max-width: 520px) { + .balance-row { + grid-template-columns: 1fr; + } + + .balance-row code { + text-align: left; + } +} diff --git a/examples/custom-google-redirect/src/vite-env.d.ts b/examples/custom-google-redirect/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/examples/custom-google-redirect/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/custom-google-redirect/tsconfig.json b/examples/custom-google-redirect/tsconfig.json new file mode 100644 index 0000000..6cc1f89 --- /dev/null +++ b/examples/custom-google-redirect/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ES2020"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "paths": { + "react": ["./node_modules/@types/react"], + "react/*": ["./node_modules/@types/react/*"] + } + }, + "include": ["src"], + "references": [] +} diff --git a/examples/custom-google-redirect/vite.config.ts b/examples/custom-google-redirect/vite.config.ts new file mode 100644 index 0000000..5f24485 --- /dev/null +++ b/examples/custom-google-redirect/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { reactAliasesForExample } from '../shared/vite-react-aliases' + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: reactAliasesForExample(import.meta.url), + }, + server: { + port: 5173, + strictPort: true, + }, +}) diff --git a/examples/node-contract-deploy-example/README.md b/examples/node-contract-deploy-example/README.md index 7c666a5..56e02e7 100644 --- a/examples/node-contract-deploy-example/README.md +++ b/examples/node-contract-deploy-example/README.md @@ -1,6 +1,6 @@ # Node Contract Deploy Example -This example signs in with an OMS wallet, compiles a small Solidity ERC-20 with +This example signs in with an OMS Wallet, compiles a small Solidity ERC-20 with a public `mint(address,uint256)` function, and submits a Polygon Amoy deployment transaction through a deployer contract. diff --git a/examples/node-contract-deploy-example/deployErc20.ts b/examples/node-contract-deploy-example/deployErc20.ts index 54b9baa..b12f063 100644 --- a/examples/node-contract-deploy-example/deployErc20.ts +++ b/examples/node-contract-deploy-example/deployErc20.ts @@ -3,7 +3,7 @@ import {mkdirSync, readFileSync, writeFileSync} from "node:fs"; import {dirname, join} from "node:path"; import readline from "node:readline/promises"; import {fileURLToPath} from "node:url"; -import {MemoryStorageManager, Networks, OMSClient} from "@0xsequence/typescript-sdk"; +import {MemoryStorageManager, Networks, OMSWallet} from "@polygonlabs/oms-wallet"; import {config as loadDotenv} from "dotenv"; import solc from "solc"; import {encodeDeployData, getContractAddress, isAddress, parseAbi} from "viem"; @@ -35,14 +35,14 @@ const defaultTokenConfig: TokenConfig = { async function main() { console.log("------------------------------------------------------------"); - console.log(" OMS wallet ERC-20 deploy example"); + console.log(" OMS Wallet ERC-20 deploy example"); console.log("------------------------------------------------------------"); console.log("network :", `${Networks.amoy.displayName} (${Networks.amoy.id})`); console.log("publishable key :", mask(publishableKey)); console.log("deployer address :", deployerAddress); console.log(); - const client = new OMSClient({ + const omsWallet = new OMSWallet({ publishableKey, storage: new MemoryStorageManager(), }); @@ -51,12 +51,12 @@ async function main() { console.log(); console.log(`[auth] startEmailAuth("${email}")`); - await client.wallet.startEmailAuth({email}); + await omsWallet.wallet.startEmailAuth({email}); const code = await prompt("Enter the code from your email: "); console.log(`[auth] completeEmailAuth("${mask(code)}")`); - const authResult = await client.wallet.completeEmailAuth({code}); + const authResult = await omsWallet.wallet.completeEmailAuth({code}); console.log(`[auth] logged in as ${authResult.walletAddress}`); console.log(); @@ -85,7 +85,7 @@ async function main() { console.log("[deploy] init code :", `${(initCode.length - 2) / 2} bytes`); console.log(); - const tx = await client.wallet.sendTransaction({ + const tx = await omsWallet.wallet.sendTransaction({ network: Networks.amoy, to: deployerAddress, abi: deployerAbi, diff --git a/examples/node-contract-deploy-example/package.json b/examples/node-contract-deploy-example/package.json index a1d6dd8..1b3ef66 100644 --- a/examples/node-contract-deploy-example/package.json +++ b/examples/node-contract-deploy-example/package.json @@ -8,7 +8,7 @@ "build": "tsc --noEmit" }, "dependencies": { - "@0xsequence/typescript-sdk": "workspace:*", + "@polygonlabs/oms-wallet": "workspace:*", "dotenv": "^17.4.2", "solc": "^0.8.35", "viem": "^2.48.4" diff --git a/examples/node/README.md b/examples/node/README.md index 68b666c..87330c9 100644 --- a/examples/node/README.md +++ b/examples/node/README.md @@ -3,7 +3,7 @@ This example consumes the SDK as a workspace package: ```ts -import { MemoryStorageManager, Networks, OMSClient } from '@0xsequence/typescript-sdk' +import { MemoryStorageManager, Networks, OMSWallet } from '@polygonlabs/oms-wallet' ``` Run it from the repository root: diff --git a/examples/node/package.json b/examples/node/package.json index 3584606..c8a89b8 100644 --- a/examples/node/package.json +++ b/examples/node/package.json @@ -8,7 +8,7 @@ "build": "tsc --noEmit" }, "dependencies": { - "@0xsequence/typescript-sdk": "workspace:*" + "@polygonlabs/oms-wallet": "workspace:*" }, "devDependencies": { "@types/node": "^22.19.19", diff --git a/examples/node/signInFlow.ts b/examples/node/signInFlow.ts index b426923..4ba7db4 100644 --- a/examples/node/signInFlow.ts +++ b/examples/node/signInFlow.ts @@ -1,11 +1,11 @@ import readline from "node:readline/promises"; -import {MemoryStorageManager, Networks, OMSClient} from "@0xsequence/typescript-sdk"; +import {MemoryStorageManager, Networks, OMSWallet} from "@polygonlabs/oms-wallet"; const publishableKey = requiredEnv("OMS_PUBLISHABLE_KEY", process.env.OMS_PUBLISHABLE_KEY); async function main() { console.log("------------------------------------------------------------"); - console.log(" OmsWallet sign-in flow"); + console.log(" OMS Wallet sign-in flow"); console.log("------------------------------------------------------------"); console.log("publishable key :", mask(publishableKey)); console.log(); @@ -13,20 +13,20 @@ async function main() { const email = await prompt("Enter your email: "); console.log(); - console.log("[setup] creating OmsWallet…"); + console.log("[setup] creating OMS Wallet…"); - const client = new OMSClient({ + const omsWallet = new OMSWallet({ publishableKey, storage: new MemoryStorageManager(), }); - console.log("[setup] ready:", client.wallet.constructor.name); + console.log("[setup] ready:", omsWallet.wallet.constructor.name); console.log(); console.log(`[step 1] startEmailAuth("${email}")`); let t = Date.now(); try { - await client.wallet.startEmailAuth({email}); + await omsWallet.wallet.startEmailAuth({email}); console.log(`[step 1] ok (${Date.now() - t}ms) — check your inbox`); } catch (err) { @@ -41,7 +41,7 @@ async function main() { t = Date.now(); try { - const result = await client.wallet.completeEmailAuth({code}); + const result = await omsWallet.wallet.completeEmailAuth({code}); console.log(`[step 2] ok (${Date.now() - t}ms)`); console.log(`[step 2] wallet ${result.walletAddress}`); @@ -54,7 +54,7 @@ async function main() { console.log(); console.log("✓ sign-in flow complete"); - await client.wallet.signMessage({ + await omsWallet.wallet.signMessage({ network: Networks.amoy, message: "test" }); diff --git a/examples/react/README.md b/examples/react/README.md index fcca4a7..4f3d41b 100644 --- a/examples/react/README.md +++ b/examples/react/README.md @@ -3,7 +3,7 @@ This example consumes the SDK as a workspace package: ```ts -import { OMSClient } from '@0xsequence/typescript-sdk' +import { OMSWallet } from '@polygonlabs/oms-wallet' ``` Run it from the repository root: diff --git a/examples/react/index.html b/examples/react/index.html index 28155c6..60dd9ba 100644 --- a/examples/react/index.html +++ b/examples/react/index.html @@ -9,7 +9,7 @@ href="https://fonts.googleapis.com/css2?family=Fustat:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet" /> - OMS SDK React Example + OMS Wallet React Example
diff --git a/examples/react/package.json b/examples/react/package.json index 6546e88..0118fce 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -11,7 +11,7 @@ "dependencies": { "react": "19.2.7", "react-dom": "19.2.7", - "@0xsequence/typescript-sdk": "workspace:*", + "@polygonlabs/oms-wallet": "workspace:*", "viem": "^2.48.4" }, "devDependencies": { diff --git a/examples/react/src/WalletKitDollarExample.tsx b/examples/react/src/WalletKitDollarExample.tsx index c70037e..61e1f6b 100644 --- a/examples/react/src/WalletKitDollarExample.tsx +++ b/examples/react/src/WalletKitDollarExample.tsx @@ -1,8 +1,8 @@ import { useEffect, useMemo, useState } from 'react' -import { Networks } from '@0xsequence/typescript-sdk' +import { Networks } from '@polygonlabs/oms-wallet' import { createPublicClient, formatUnits, http, isAddress, parseUnits } from 'viem' import type { Address } from 'viem' -import { oms } from './omsClient' +import { omsWallet } from './omsWallet' import { walletKitDollarAbi } from './walletKitDollarContract' const AMOY_RPC_URL = 'https://rpc-amoy.polygon.technology' @@ -31,7 +31,7 @@ export function WalletKitDollarExample() { }), []) useEffect(() => { - const restoredAddress = oms.wallet.walletAddress ?? '' + const restoredAddress = omsWallet.wallet.walletAddress ?? '' setWalletAddress(restoredAddress) if (isAddress(restoredAddress)) { void refreshBalance(restoredAddress) @@ -61,7 +61,7 @@ export function WalletKitDollarExample() { const activeWallet = requireWalletAddress() clearLastTransaction() - const tx = await oms.wallet.sendTransaction({ + const tx = await omsWallet.wallet.sendTransaction({ network: Networks.amoy, to: WKUSD_CONTRACT_ADDRESS, abi: walletKitDollarAbi, @@ -94,7 +94,7 @@ export function WalletKitDollarExample() { clearLastTransaction() - const tx = await oms.wallet.sendTransaction({ + const tx = await omsWallet.wallet.sendTransaction({ network: Networks.amoy, to: WKUSD_CONTRACT_ADDRESS, abi: walletKitDollarAbi, @@ -123,7 +123,7 @@ export function WalletKitDollarExample() { } function requireWalletAddress(): Address { - const activeWallet = oms.wallet.walletAddress ?? walletAddress + const activeWallet = omsWallet.wallet.walletAddress ?? walletAddress if (!isAddress(activeWallet)) { throw new Error('Active wallet address is not a valid EVM address.') } diff --git a/examples/react/src/main.tsx b/examples/react/src/main.tsx index 96cccb4..47be721 100644 --- a/examples/react/src/main.tsx +++ b/examples/react/src/main.tsx @@ -2,16 +2,16 @@ import React, { useEffect, useRef, useState } from 'react' import { createRoot } from 'react-dom/client' import { Networks, - supportedNetworks, + OmsRelayOidcProviders, type FeeOptionSelection, type FeeOptionWithBalance, type AccessGrant, type Network, - type OMSClientSessionExpiredEvent, - type OmsWallet, + type OMSWalletSessionExpiredEvent, + type WalletAccount, type PendingWalletSelection, type WalletActivationResult, -} from '@0xsequence/typescript-sdk' +} from '@polygonlabs/oms-wallet' import './styles.css' import { EmailCodeForm, @@ -24,8 +24,8 @@ import { } from '../../shared/example-components' import { formatCount, - formatLoginType, formatOidcProvider, + formatSessionAuth, formatSessionExpiry, formatWalletType, hasOidcCallbackParams, @@ -34,7 +34,7 @@ import { type OidcRedirectProvider, } from '../../shared/example-utils' import { useSessionPreferences } from '../../shared/use-session-preferences' -import { TEST_SESSION_LIFETIME_SECONDS, oms } from './omsClient' +import { TEST_SESSION_LIFETIME_SECONDS, omsWallet } from './omsWallet' import { WalletKitDollarExample } from './WalletKitDollarExample' type Step = 'email' | 'code' | 'wallet-selection' | 'wallet' @@ -47,6 +47,7 @@ const DEFAULT_MESSAGE = 'test' const DEFAULT_TX_TO = '0xE5E8B483FfC05967FcFed58cc98D053265af6D99' const MANUAL_WALLET_SELECTION_KEY = 'oms-demo-manual-wallet-selection' const SESSION_LIFETIME_SECONDS_KEY = 'oms-demo-session-lifetime-seconds-v2' +const supportedNetworks = Object.values(Networks) function App() { const [step, setStep] = useState('email') @@ -62,7 +63,7 @@ function App() { const [lastTransactionHash, setLastTransactionHash] = useState('') const [lastTransactionExplorerUrl, setLastTransactionExplorerUrl] = useState('') const [feeOptions, setFeeOptions] = useState([]) - const [managedWallets, setManagedWallets] = useState([]) + const [managedWallets, setManagedWallets] = useState([]) const [newWalletReference, setNewWalletReference] = useState('') const [accessGrants, setAccessGrants] = useState([]) const [pendingWalletSelection, setPendingWalletSelection] = useState(null) @@ -71,13 +72,13 @@ function App() { const [walletStatus, setWalletStatus] = useState('') const [activeWalletStatus, setActiveWalletStatus] = useState('') const [accessStatus, setAccessStatus] = useState('') - const [sessionExpiredPrompt, setSessionExpiredPrompt] = useState(null) + const [sessionExpiredPrompt, setSessionExpiredPrompt] = useState(null) const [isBusy, setIsBusy] = useState(false) const oidcCallbackStarted = useRef(false) const feeSelection = useRef(null) const selectedNetwork = supportedNetworks.find(network => network.id === selectedNetworkId) ?? Networks.amoy - const session = oms.wallet.session + const session = omsWallet.wallet.session const { useManualWalletSelection, setUseManualWalletSelection, @@ -92,12 +93,12 @@ function App() { }) useEffect(() => { - return oms.wallet.onSessionExpired(showSessionExpired) + return omsWallet.wallet.onSessionExpired(showSessionExpired) }, []) useEffect(() => { - if (oms.wallet.walletAddress) { - setWalletAddress(oms.wallet.walletAddress) + if (omsWallet.wallet.walletAddress) { + setWalletAddress(omsWallet.wallet.walletAddress) setStep('wallet') setWalletStatus('Wallet session restored.') return @@ -108,7 +109,7 @@ function App() { oidcCallbackStarted.current = true void completeOidcRedirect() } - }, [oms]) + }, [omsWallet]) useEffect(() => { feeSelection.current?.reject(new Error('Network changed')) @@ -143,7 +144,10 @@ function App() { if (!email.trim()) return await run('Sending code...', setEmailAuthStatus, async () => { setPendingWalletSelection(null) - await oms.wallet.startEmailAuth({ email: email.trim() }) + await omsWallet.wallet.startEmailAuth({ + email: email.trim(), + sessionLifetimeSeconds, + }) setStep('code') setEmailAuthStatus('Code sent. Check your email.') }) @@ -152,10 +156,9 @@ function App() { async function completeEmailAuth() { if (!code.trim()) return await run('Completing sign-in...', setEmailAuthStatus, async () => { - const result = await oms.wallet.completeEmailAuth({ + const result = await omsWallet.wallet.completeEmailAuth({ code: code.trim(), walletSelection, - sessionLifetimeSeconds, }) handleAuthCompletion(result, 'Email login complete.') }) @@ -166,8 +169,8 @@ function App() { await run(`Redirecting to ${label}...`, setRedirectStatus, async () => { saveSessionPreferences() setPendingWalletSelection(null) - await oms.wallet.signInWithOidcRedirect({ - provider, + await omsWallet.wallet.signInWithOidcRedirect({ + provider: provider === 'google' ? OmsRelayOidcProviders.google : OmsRelayOidcProviders.apple, walletSelection, sessionLifetimeSeconds, }) @@ -176,13 +179,13 @@ function App() { async function completeOidcRedirect() { await run('Completing redirect sign-in...', setRedirectStatus, async () => { - const result = await oms.wallet.completeOidcRedirectAuth() + const result = await omsWallet.wallet.completeOidcRedirectAuth() if (result) { handleAuthCompletion(result, 'Redirect login complete.') return } - const restoredAddress = oms.wallet.walletAddress ?? '' + const restoredAddress = omsWallet.wallet.walletAddress ?? '' setWalletAddress(restoredAddress) setStep(restoredAddress ? 'wallet' : 'email') setWalletStatus(restoredAddress ? 'Wallet ready.' : '') @@ -209,7 +212,7 @@ function App() { setWalletStatus(status) } - function showSessionExpired(event: OMSClientSessionExpiredEvent) { + function showSessionExpired(event: OMSWalletSessionExpiredEvent) { feeSelection.current?.reject(new Error('Session expired')) feeSelection.current = null setFeeOptions([]) @@ -223,14 +226,14 @@ function App() { setCode('') setStep('email') setEmailAuthStatus( - event.session.sessionEmail - ? `Session expired for ${event.session.sessionEmail}.` + event.session.auth?.email + ? `Session expired for ${event.session.auth.email}.` : 'Session expired. Enter an email to continue.', ) setRedirectStatus('') setWalletStatus('') - if (event.session.sessionEmail) { - setEmail(event.session.sessionEmail) + if (event.session.auth?.email) { + setEmail(event.session.auth.email) } setSessionExpiredPrompt(event) } @@ -239,13 +242,14 @@ function App() { if (!sessionExpiredPrompt) return const expiredSession = sessionExpiredPrompt.session - if (expiredSession.loginType === 'google-auth') { + const auth = expiredSession.auth + if (auth?.type === 'oidc' && auth.provider === 'google') { await run('Redirecting to Google...', setRedirectStatus, async () => { setSessionExpiredPrompt(null) saveSessionPreferences() setPendingWalletSelection(null) - await oms.wallet.signInWithOidcRedirect({ - provider: 'google', + await omsWallet.wallet.signInWithOidcRedirect({ + provider: OmsRelayOidcProviders.google, walletSelection, sessionLifetimeSeconds, }) @@ -253,13 +257,13 @@ function App() { return } - if (expiredSession.loginType === 'oidc') { + if (auth?.type === 'oidc' && auth.provider === 'apple') { await run('Redirecting to Apple...', setRedirectStatus, async () => { setSessionExpiredPrompt(null) saveSessionPreferences() setPendingWalletSelection(null) - await oms.wallet.signInWithOidcRedirect({ - provider: 'apple', + await omsWallet.wallet.signInWithOidcRedirect({ + provider: OmsRelayOidcProviders.apple, walletSelection, sessionLifetimeSeconds, }) @@ -267,12 +271,13 @@ function App() { return } - if (expiredSession.loginType === 'email' && expiredSession.sessionEmail) { + if (auth?.type === 'email' && auth.email) { + const email = auth.email await run('Sending code...', setEmailAuthStatus, async () => { setSessionExpiredPrompt(null) setPendingWalletSelection(null) - setEmail(expiredSession.sessionEmail ?? '') - await oms.wallet.startEmailAuth({ email: expiredSession.sessionEmail ?? '' }) + setEmail(email) + await omsWallet.wallet.startEmailAuth({ email }) setStep('code') setEmailAuthStatus('Code sent. Check your email.') }) @@ -289,7 +294,7 @@ function App() { setStep('email') } - async function selectPendingWallet(wallet: OmsWallet) { + async function selectPendingWallet(wallet: WalletAccount) { if (!pendingWalletSelection) return await run('Selecting wallet...', setEmailAuthStatus, async () => { const result = await pendingWalletSelection.selectWallet({ walletId: wallet.id }) @@ -307,7 +312,7 @@ function App() { async function cancelPendingWalletSelection() { await run('Cancelling wallet selection...', setEmailAuthStatus, async () => { - await oms.wallet.signOut() + await omsWallet.wallet.signOut() setPendingWalletSelection(null) setWalletAddress('') setCode('') @@ -318,7 +323,7 @@ function App() { async function signMessage() { await run('Signing message...', setWalletStatus, async () => { - const signature = await oms.wallet.signMessage({ + const signature = await omsWallet.wallet.signMessage({ network: selectedNetwork, message, }) @@ -332,7 +337,7 @@ function App() { setFeeOptions([]) setLastTransactionExplorerUrl('') try { - const tx = await oms.wallet.sendTransaction({ + const tx = await omsWallet.wallet.sendTransaction({ network: selectedNetwork, to: transactionTo as `0x${string}`, value: BigInt(transactionValue || '0'), @@ -350,15 +355,15 @@ function App() { async function loadManagedWallets() { await run('Loading wallets...', setActiveWalletStatus, async () => { - const wallets = await oms.wallet.listWallets() + const wallets = await omsWallet.wallet.listWallets() setManagedWallets(wallets) setActiveWalletStatus(`Loaded ${formatCount(wallets.length, 'wallet')}.`) }) } - async function useManagedWallet(wallet: OmsWallet) { + async function useManagedWallet(wallet: WalletAccount) { await run('Switching wallet...', setActiveWalletStatus, async () => { - const result = await oms.wallet.useWallet({ walletId: wallet.id }) + const result = await omsWallet.wallet.useWallet({ walletId: wallet.id }) setWalletAddress(result.walletAddress) clearWalletOperationResults() setAccessGrants([]) @@ -373,7 +378,7 @@ function App() { async function createManagedWallet() { await run('Creating wallet...', setActiveWalletStatus, async () => { const reference = newWalletReference.trim() - const result = await oms.wallet.createWallet({ + const result = await omsWallet.wallet.createWallet({ reference: reference || undefined, }) setWalletAddress(result.walletAddress) @@ -391,7 +396,7 @@ function App() { async function loadAccess() { await run('Loading access...', setAccessStatus, async () => { - const grants = await oms.wallet.listAccess() + const grants = await omsWallet.wallet.listAccess() setAccessGrants(grants) setAccessStatus(`Loaded ${formatCount(grants.length, 'access grant')}.`) }) @@ -404,7 +409,7 @@ function App() { } await run('Revoking access...', setAccessStatus, async () => { - await oms.wallet.revokeAccess({ targetCredentialId: grant.credentialId }) + await omsWallet.wallet.revokeAccess({ targetCredentialId: grant.credentialId }) setAccessGrants(current => current.filter(item => item.credentialId !== grant.credentialId)) setAccessStatus('Access grant revoked.') }) @@ -412,7 +417,7 @@ function App() { async function getIdToken() { await run('Getting ID token...', setWalletStatus, async () => { - const idToken = await oms.wallet.getIdToken() + const idToken = await omsWallet.wallet.getIdToken() setLastIdToken(idToken) setWalletStatus('ID token issued.') }) @@ -441,7 +446,7 @@ function App() { async function signOut() { await run('Signing out...', setWalletStatus, async () => { - await oms.wallet.signOut() + await omsWallet.wallet.signOut() setCode('') setPendingWalletSelection(null) setWalletAddress('') @@ -480,7 +485,7 @@ function App() {
-

OMS Client Typescript SDK

+

OMS Wallet TypeScript SDK

Wallet Demo

{step === 'email' && (
- Login - {formatLoginType(session.loginType)} + Auth + {formatSessionAuth(session.auth)}
- Email - {session.sessionEmail ?? 'Unknown'} + Account + {session.auth?.email ?? 'Unknown'}
Expires diff --git a/examples/react/src/omsClient.ts b/examples/react/src/omsWallet.ts similarity index 59% rename from examples/react/src/omsClient.ts rename to examples/react/src/omsWallet.ts index 36a1e3c..84d035b 100644 --- a/examples/react/src/omsClient.ts +++ b/examples/react/src/omsWallet.ts @@ -1,8 +1,8 @@ -import { OMSClient } from '@0xsequence/typescript-sdk' +import { OMSWallet } from '@polygonlabs/oms-wallet' import { PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 -export const oms = new OMSClient({ +export const omsWallet = new OMSWallet({ publishableKey: PUBLISHABLE_KEY, }) diff --git a/examples/shared/example-components.tsx b/examples/shared/example-components.tsx index cc562f4..87520f7 100644 --- a/examples/shared/example-components.tsx +++ b/examples/shared/example-components.tsx @@ -1,11 +1,12 @@ import type { FeeOptionWithBalance, - OMSClientSessionExpiredEvent, - OmsWallet, + OMSWalletSessionExpiredEvent, + WalletAccount, PendingWalletSelection, -} from '@0xsequence/typescript-sdk' +} from '@polygonlabs/oms-wallet' import { canAffordFeeOption, + formatSessionAuth, formatOidcProvider, formatWalletType, type OidcRedirectProvider, @@ -195,7 +196,7 @@ export function WalletSelectionPanel({ pendingWalletSelection: PendingWalletSelection authStatus?: string disabled: boolean - onSelectWallet: (wallet: OmsWallet) => void + onSelectWallet: (wallet: WalletAccount) => void onCreateWallet: () => void onCancel: () => void }) { @@ -294,7 +295,7 @@ export function SessionExpiredDialog({ onReauthenticate, onDismiss, }: { - event: OMSClientSessionExpiredEvent + event: OMSWalletSessionExpiredEvent disabled: boolean onReauthenticate: () => void onDismiss: () => void @@ -311,19 +312,17 @@ export function SessionExpiredDialog({

Your wallet session has expired. Reauthenticate to continue using this wallet.

- {event.session.sessionEmail && ( + {event.session.auth?.email && (

- Account {event.session.sessionEmail} + Account {event.session.auth.email}

)}

- {event.session.loginType === 'google-auth' - ? 'You will be redirected to Google with the same account selected.' - : event.session.loginType === 'oidc' - ? 'You will be redirected to Apple.' - : event.session.loginType === 'email' && event.session.sessionEmail - ? 'A new sign-in code will be sent to the same email address.' - : 'Sign in again to continue.'} + {event.session.auth?.type === 'oidc' + ? `You will be redirected to ${formatSessionAuth(event.session.auth)}.` + : event.session.auth?.type === 'email' && event.session.auth.email + ? 'A new sign-in code will be sent to the same email address.' + : 'Sign in again to continue.'}

)} {activeOmsSessionAddress && (showOidcAuth || showEmailAuth) && ( @@ -503,7 +536,7 @@ export function App() {