diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index a4df170..254c0b9 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -46,6 +46,8 @@ jobs: run: pnpm build:example env: GITHUB_PAGES: 'true' + VITE_OMS_PUBLIC_API_KEY: ${{ secrets.OMS_PUBLIC_API_KEY }} + VITE_OMS_PROJECT_ID: ${{ secrets.OMS_PROJECT_ID }} - name: Stage Pages artifact run: | diff --git a/AGENTS.md b/AGENTS.md index 848cf29..da1c279 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Project Overview -This repository is a pnpm workspace for the OMS TypeScript SDK. The root package exports the `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 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. ## Setup and Tooling @@ -56,7 +56,7 @@ This repository is a pnpm workspace for the OMS TypeScript SDK. The root package - 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 network names and chain IDs going through `NetworkBindings` instead of ad hoc string conversion. +- Keep supported network metadata and chain ID lookup going through `src/networks.ts`, `Networks`, `supportedNetworks`, `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. ## Testing Guidance @@ -78,9 +78,9 @@ This repository is a pnpm workspace for the OMS TypeScript SDK. The root package ## 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_PROJECT_ACCESS_KEY`; keep local overrides in `examples/react/.env.local`. +- The React example uses `examples/react/.env.example` for `VITE_OMS_PUBLIC_API_KEY` and `VITE_OMS_PROJECT_ID`; keep local overrides in `examples/react/.env.local`. - Treat credential signing, nonce handling, OIDC redirect state cleanup, session persistence, transaction execution/status polling, and access revocation as high-risk paths. Prefer focused regression tests for changes in these areas. -- CI may provide `OMS_PROJECT_ACCESS_KEY` for tests. Do not require that secret for ordinary local unit tests unless the test explicitly needs an external boundary. +- GitHub Pages may provide `OMS_PUBLIC_API_KEY` and `OMS_PROJECT_ID` secrets for the deployed React example. Do not require those secrets for ordinary local unit tests unless the test explicitly needs an external boundary. ## Agent Workflow Rules diff --git a/API.md b/API.md index 2eaf1a9..582ac8d 100644 --- a/API.md +++ b/API.md @@ -4,6 +4,7 @@ - [OMSClient](#omsclient) - [Constructor](#constructor) + - [supportedNetworks](#supportednetworks) - [WalletClient](#walletclient) - [walletAddress](#walletaddress) - [session](#session) @@ -53,6 +54,9 @@ - [TokenBalancesResult](#tokenbalancesresult) - [TokenBalancesPage](#tokenbalancespage) - [TokenBalance](#tokenbalance) + - [TokenContractInfo](#tokencontractinfo) + - [TokenMetadata](#tokenmetadata) + - [TokenMetadataAsset](#tokenmetadataasset) - [AbiArg](#abiarg) - [WalletType](#wallettype) @@ -63,16 +67,20 @@ The top-level entry point for the SDK. ```typescript -import { OMSClient } from 'typescript-sdk' +import { OMSClient } from '@0xsequence/typescript-sdk' -const oms = new OMSClient({ projectAccessKey: 'your-key' }) +const oms = new OMSClient({ + publicApiKey: 'your-public-api-key', + projectId: 'your-project-id', +}) ``` ### Constructor ```typescript new OMSClient(params: { - projectAccessKey: string + publicApiKey: string + projectId: string environment?: OmsEnvironment storage?: StorageManager redirectAuthStorage?: StorageManager @@ -84,7 +92,8 @@ new OMSClient(params: { | Name | Type | Required | Description | |---|---|---|---| -| `projectAccessKey` | `string` | Yes | Your OMS project access key. | +| `publicApiKey` | `string` | Yes | Your OMS public API key. | +| `projectId` | `string` | Yes | Your OMS project ID. Used as the WaaS signing scope for wallet requests and OIDC redirect state. | | `environment` | `OmsEnvironment` | No | API endpoint configuration. Defaults to the SDK's configured OMS endpoints. | | `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 verifier/state. Defaults to `sessionStorage` when available. | @@ -96,8 +105,15 @@ new OMSClient(params: { |---|---|---| | `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 + +```typescript +oms.supportedNetworks: readonly Network[] +``` + +Returns the supported network registry. Each entry has `id`, `name`, `nativeTokenSymbol`, and `explorerUrl`. ## WalletClient @@ -165,7 +181,10 @@ completeEmailAuth(params: { code: string walletType?: WalletType autoActivate?: boolean -}): Promise<{ walletAddress: Address; wallet: OmsWallet; wallets: OmsWallet[]; credential: WalletCredential }> +}): Promise< + | { walletAddress: Address; wallet: OmsWallet; wallets: OmsWallet[]; credential: WalletCredential } + | { wallets: OmsWallet[]; credential: WalletCredential } +> ``` Verifies the OTP code and activates a wallet. Must be called after [`startEmailAuth`](#startemailauth). @@ -234,7 +253,10 @@ completeOidcRedirectAuth(params: { cleanUrl?: boolean replaceUrl?: (url: string) => void autoActivate?: boolean -}): Promise<{ walletAddress: Address; wallet: OmsWallet; wallets: OmsWallet[]; credential: WalletCredential }> +}): Promise< + | { walletAddress: Address; wallet: OmsWallet; wallets: OmsWallet[]; credential: WalletCredential } + | { wallets: OmsWallet[]; credential: WalletCredential } +> ``` Completes an OIDC redirect flow by validating the persisted state nonce, exchanging the authorization code with WaaS using a one-week session lifetime, and activating an existing wallet or creating one. Pass `autoActivate: false` to return `{ wallets, credential }` for app-driven wallet selection. `cleanUrl` removes OAuth query parameters after successful completion; outside a browser, pass `replaceUrl`. @@ -287,7 +309,6 @@ Clears the wallet session metadata from storage and clears the active credential ```typescript await oms.wallet.signOut() -// Navigate to sign-in screen ``` --- @@ -337,7 +358,7 @@ Signs an arbitrary message using the active wallet session credential. | Name | Type | Description | |---|---|---| -| `network` | `Network` | The network for the signing context. Accepts a chain name string, chain ID bigint, or viem `Chain` object. See [Network](#network). | +| `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. @@ -345,11 +366,8 @@ Signs an arbitrary message using the active wallet session credential. **Example** ```typescript -const sig = await oms.wallet.signMessage({ network: 'polygon', message: '0xdeadbeef' }) - -// Using a viem Chain object -import { polygon } from 'viem/chains' -const sig = await oms.wallet.signMessage({ network: polygon, message: '0xdeadbeef' }) +import { Networks } from '@0xsequence/typescript-sdk' +const sigFromNetwork = await oms.wallet.signMessage({ network: Networks.polygon, message: 'some message to sing' }) ``` --- @@ -421,13 +439,15 @@ Fetches the latest WaaS status for a prepared/executed transaction. This is usef sendTransaction(params: SendNativeTransactionParams): Promise ``` -Sends native tokens (ETH, MATIC, etc.) to an address. +Sends native tokens (ETH, POL, etc.) to an address. ```typescript +import { parseUnits } from 'viem' + const tx = await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xRecipient', - value: 1_000_000_000_000_000_000n, // 1 MATIC in wei + value: parseUnits('1', 18), // 1 POL }) ``` @@ -441,7 +461,7 @@ Sends a transaction with arbitrary calldata as a hex string. Use this when you h ```typescript const tx = await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xContract', data: '0xa9059cbb000000000000000000000000...', }) @@ -456,6 +476,8 @@ sendTransaction(params: SendContractTransactionParams ``` -Fetches token balances for a wallet on a given chain and contract (first page, up to 40 entries). +Fetches token balances for a wallet on a given network. Omit `contractAddress` to query balances across contracts; provide it to filter to one token contract. The default request returns page `0` with up to `40` entries. When `includeMetadata` is `true`, token display data is returned on `contractInfo` and `tokenMetadata`; ERC-20 decimals are available as `contractInfo.decimals`. **Parameters** | Name | Type | Description | |---|---|---| -| `chainId` | `string` | Numeric chain ID as a string, e.g. `"137"` for Polygon, `"1"` for Ethereum mainnet. | -| `contractAddress` | `string` | The token contract address to query. | +| `network` | `Network` | The network to query. Use an exported registry value such as `Networks.polygon`. | +| `contractAddress` | `string` | Optional token contract filter. Omit to query balances across contracts. | | `walletAddress` | `string` | The wallet address whose balances to fetch. Use `oms.wallet.walletAddress` after checking it is defined. | | `includeMetadata` | `boolean` | When `true`, the response includes token metadata such as name, symbol, and decimals. | +| `page` | `{ page?: number; pageSize?: number }` | Optional pagination request. Defaults to `{ page: 0, pageSize: 40 }`. | **Returns** `Promise` — see [TokenBalancesResult](#tokenbalancesresult). @@ -642,8 +671,7 @@ const { walletAddress } = oms.wallet if (!walletAddress) throw new Error('No active wallet session') const result = await oms.indexer.getTokenBalances({ - chainId: '137', - contractAddress: '0xTokenContract', + network: Networks.polygon, walletAddress, includeMetadata: true, }) @@ -659,7 +687,7 @@ for (const b of result.balances) { ```typescript getNativeTokenBalance(params: { - chainId: string + network: Network walletAddress: string }): Promise ``` @@ -713,18 +741,39 @@ Use `isOmsSdkError(err)` or `err instanceof OmsSdkError` to branch on structured ### Network ```typescript -type Network = string | bigint | Chain +interface Network { + readonly id: number + readonly name: string + readonly nativeTokenSymbol: string + readonly explorerUrl: string +} ``` -Accepted by all transaction and signing methods. The SDK resolves the appropriate chain name regardless of which form you pass: +A supported OMS network entry. The SDK exports `Networks`, `supportedNetworks`, `findNetworkById(id)`, and `findNetworkByName(name)`. -| Form | Example | -|---|---| -| Chain name string | `'polygon'`, `'mainnet'`, `'arbitrum'` | -| Chain ID as bigint | `137n`, `1n`, `42161n` | -| viem `Chain` object | `polygon`, `mainnet` (from `viem/chains`) | +```typescript +findNetworkById(id: number): Network | undefined +findNetworkByName(name: string): Network | undefined +``` ---- +| Key | id | name | nativeTokenSymbol | explorerUrl | +|---|---:|---|---|---| +| `Networks.mainnet` | 1 | `mainnet` | `ETH` | `https://etherscan.io` | +| `Networks.sepolia` | 11155111 | `sepolia` | `ETH` | `https://sepolia.etherscan.io` | +| `Networks.polygon` | 137 | `polygon` | `POL` | `https://polygonscan.com` | +| `Networks.amoy` | 80002 | `amoy` | `POL` | `https://amoy.polygonscan.com` | +| `Networks.arbitrum` | 42161 | `arbitrum` | `ETH` | `https://arbiscan.io` | +| `Networks.arbitrumSepolia` | 421614 | `arbitrum-sepolia` | `ETH` | `https://sepolia.arbiscan.io` | +| `Networks.optimism` | 10 | `optimism` | `ETH` | `https://optimistic.etherscan.io` | +| `Networks.optimismSepolia` | 11155420 | `optimism-sepolia` | `ETH` | `https://sepolia-optimism.etherscan.io` | +| `Networks.base` | 8453 | `base` | `ETH` | `https://basescan.org` | +| `Networks.baseSepolia` | 84532 | `base-sepolia` | `ETH` | `https://sepolia.basescan.org` | +| `Networks.bsc` | 56 | `bsc` | `BNB` | `https://bscscan.com` | +| `Networks.bscTestnet` | 97 | `bsc-testnet` | `BNB` | `https://testnet.bscscan.com` | +| `Networks.arbitrumNova` | 42170 | `arbitrum-nova` | `ETH` | `https://nova.arbiscan.io` | +| `Networks.avalanche` | 43114 | `avalanche` | `AVAX` | `https://subnets.avax.network/c-chain` | +| `Networks.avalancheTestnet` | 43113 | `avalanche-testnet` | `AVAX` | `https://subnets-test.avax.network/c-chain` | +| `Networks.katana` | 747474 | `katana` | `ETH` | `https://katanascan.com` | ### OmsEnvironment @@ -733,7 +782,6 @@ interface OmsEnvironment { walletApiUrl: string indexerUrlTemplate: string auth?: { - waasAuthScope?: string oidcProviders?: Record } } @@ -742,8 +790,7 @@ interface OmsEnvironment { | Field | Type | Description | |---|---|---| | `walletApiUrl` | `string` | Base URL of the WaaS Wallet RPC host. | -| `indexerUrlTemplate` | `string` | URL template for the Indexer API. `{value}` is replaced with the chain ID at request time, e.g. `"https://indexer.example.com/{value}"`. | -| `auth.waasAuthScope` | `string` | WaaS credential auth scope used in signed wallet API requests. Defaults to `proj_1`. | +| `indexerUrlTemplate` | `string` | URL template for the Indexer API. `{value}` is replaced with the selected network name, e.g. `"https://indexer.example.com/{value}"`. | | `auth.oidcProviders` | `Record` | OIDC provider configurations addressable by provider key. | The default is exported as `defaultOmsEnvironment` and includes the `google` OIDC provider. @@ -1050,9 +1097,16 @@ interface TokenBalance { accountAddress?: string tokenId?: string balance?: string + balanceUSD?: string + priceUSD?: string + priceUpdatedAt?: string blockHash?: string blockNumber?: number chainId?: number + uniqueCollectibles?: string + isSummary?: boolean + contractInfo?: TokenContractInfo + tokenMetadata?: TokenMetadata } ``` @@ -1063,9 +1117,95 @@ interface TokenBalance { | `accountAddress` | `string` | Wallet address this balance belongs to. | | `tokenId` | `string` | For ERC-721/ERC-1155 tokens, the token ID. | | `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 + +```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 +} +``` + +Contract-level metadata returned by the Indexer when `includeMetadata` is `true`. + +--- + +### TokenMetadata + +```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 +} +``` + +Token-level metadata returned by the Indexer when available. + +--- + +### TokenMetadataAsset + +```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 +} +``` + +Media asset metadata associated with token metadata when returned. --- diff --git a/README.md b/README.md index 091b91f..441f279 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A TypeScript SDK for the OMS (Open Money Stack) platform. Provides email and OID ## Usage -This SDK is not published as an npm package yet. In this repository it is consumed as the local pnpm workspace package `typescript-sdk`. +For local development in this repository, install dependencies and build the workspace package: From the repository root: @@ -20,15 +20,21 @@ A deployed React example is available at [https://0xsequence.github.io/typescrip To run it locally from the repository root: ```bash +cp examples/react/.env.example examples/react/.env.local +# Fill VITE_OMS_PUBLIC_API_KEY and VITE_OMS_PROJECT_ID in examples/react/.env.local pnpm dev:example ``` ## Quick Start ```typescript -import { OMSClient } from 'typescript-sdk' +import { Networks, OMSClient } from '@0xsequence/typescript-sdk' +import { parseUnits } from 'viem' -const oms = new OMSClient({ projectAccessKey: 'your-project-access-key' }) +const oms = new OMSClient({ + publicApiKey: 'your-public-api-key', + projectId: 'your-project-id', +}) // 1. Send a one-time code to the user's email await oms.wallet.startEmailAuth({ email: 'user@example.com' }) @@ -42,9 +48,9 @@ console.log('Credential:', credential.credentialId) // 4. Send a transaction const tx = await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xRecipient', - value: 1_000_000_000_000_000_000n, // 1 MATIC + value: parseUnits('1', 18), // 1 POL }) console.log(tx.txnHash ?? tx.txnId) ``` @@ -62,40 +68,22 @@ console.log(tx.txnHash ?? tx.txnId) OMS supports email-based OTP and OIDC authorization-code PKCE 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. 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 }`. -The session stores wallet metadata in the configured storage, including the wallet address, credential expiry, login type, and email returned by the wallet API. 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. - -To end the session, call `await oms.wallet.signOut()`. - -```typescript -const { walletAddress, expiresAt, loginType, sessionEmail } = oms.wallet.session -``` - -Apps that need wallet selection can opt out of automatic activation: - -```typescript -const { wallets, credential } = await oms.wallet.completeEmailAuth({ - code, - autoActivate: false, -}) - -// Show wallets in your UI, then either: -await oms.wallet.useWallet({ walletId: wallets[0].id }) - -// Or create and activate a new wallet: -await oms.wallet.createWallet({ type: WalletType.Ethereum }) -``` - ### OIDC Redirect Auth Google redirect auth is configured on the default environment. The redirect auth APIs are provider-neutral, so custom environments can add or replace providers. ```typescript -const oms = new OMSClient({ projectAccessKey: 'your-key' }) +const oms = new OMSClient({ + publicApiKey: 'your-public-api-key', + projectId: 'your-project-id', +}) ``` For routers such as React Router or Next.js, use the explicit start/complete methods: @@ -123,22 +111,59 @@ void oms.wallet.signInWithOidcRedirect({ provider: 'google' }) Pending redirect state is stored in `sessionStorage` by default. Final wallet session metadata continues to use the configured SDK storage. +### 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. + +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. + +```typescript +const walletAddress = oms.wallet.walletAddress +const { expiresAt, loginType, sessionEmail } = oms.wallet.session +``` + +Pending email OTP and OIDC redirect state are not exposed through `session`; use the auth method results to drive pending UI. + +To end the session, call: + +```typescript +await oms.wallet.signOut() +``` + ## Networks -The `network` parameter on all transaction and signing methods accepts any of three forms: +The SDK exports `Networks`, `supportedNetworks`, `findNetworkById(id)`, and `findNetworkByName(name)` for the networks currently configured by OMS. Each network has `id`, `name`, `nativeTokenSymbol`, and `explorerUrl`. + +The `network` parameter on all transaction and signing methods accepts a `Network` from the SDK registry: ```typescript -// Chain name string -await oms.wallet.signMessage({ network: 'polygon', message: '0xdeadbeef' }) +import { Networks, findNetworkById, supportedNetworks } from '@0xsequence/typescript-sdk' -// Chain ID as bigint -await oms.wallet.signMessage({ network: 137n, message: '0xdeadbeef' }) +await oms.wallet.signMessage({ network: Networks.polygon, message: 'some message to sing' }) -// viem Chain object -import { polygon } from 'viem/chains' -await oms.wallet.signMessage({ network: polygon, message: '0xdeadbeef' }) +console.log(supportedNetworks) +console.log(findNetworkById(80002)) // Networks.amoy ``` +| Key | id | name | native token | explorerUrl | +|---|---:|---|---|---| +| `Networks.mainnet` | 1 | `mainnet` | ETH | `https://etherscan.io` | +| `Networks.sepolia` | 11155111 | `sepolia` | ETH | `https://sepolia.etherscan.io` | +| `Networks.polygon` | 137 | `polygon` | POL | `https://polygonscan.com` | +| `Networks.amoy` | 80002 | `amoy` | POL | `https://amoy.polygonscan.com` | +| `Networks.arbitrum` | 42161 | `arbitrum` | ETH | `https://arbiscan.io` | +| `Networks.arbitrumSepolia` | 421614 | `arbitrum-sepolia` | ETH | `https://sepolia.arbiscan.io` | +| `Networks.optimism` | 10 | `optimism` | ETH | `https://optimistic.etherscan.io` | +| `Networks.optimismSepolia` | 11155420 | `optimism-sepolia` | ETH | `https://sepolia-optimism.etherscan.io` | +| `Networks.base` | 8453 | `base` | ETH | `https://basescan.org` | +| `Networks.baseSepolia` | 84532 | `base-sepolia` | ETH | `https://sepolia.basescan.org` | +| `Networks.bsc` | 56 | `bsc` | BNB | `https://bscscan.com` | +| `Networks.bscTestnet` | 97 | `bsc-testnet` | BNB | `https://testnet.bscscan.com` | +| `Networks.arbitrumNova` | 42170 | `arbitrum-nova` | ETH | `https://nova.arbiscan.io` | +| `Networks.avalanche` | 43114 | `avalanche` | AVAX | `https://subnets.avax.network/c-chain` | +| `Networks.avalancheTestnet` | 43113 | `avalanche-testnet` | AVAX | `https://subnets-test.avax.network/c-chain` | +| `Networks.katana` | 747474 | `katana` | ETH | `https://katanascan.com` | + ## Sending Transactions `sendTransaction` has three overloaded signatures to cover the most common patterns. @@ -146,10 +171,12 @@ await oms.wallet.signMessage({ network: polygon, message: '0xdeadbeef' }) ### Native Token Transfer ```typescript +import { parseUnits } from 'viem' + const tx = await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xRecipient', - value: 1_000_000_000_000_000_000n, // 1 MATIC in wei + value: parseUnits('1', 18), // 1 POL }) ``` @@ -157,7 +184,7 @@ const tx = await oms.wallet.sendTransaction({ ```typescript const tx = await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xContract', data: '0xa9059cbb000000000000000000000000...', }) @@ -168,6 +195,8 @@ const tx = await oms.wallet.sendTransaction({ Pass an ABI and function name — the SDK encodes the calldata automatically using viem. ```typescript +import { parseUnits } from 'viem' + const erc20Abi = [ { name: 'transfer', @@ -180,11 +209,11 @@ const erc20Abi = [ ] as const const tx = await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xTokenContract', abi: erc20Abi, functionName: 'transfer', - args: ['0xRecipient', 1_000_000_000_000_000_000n], + args: ['0xRecipient', parseUnits('1', 18)], }) ``` @@ -197,10 +226,12 @@ To return immediately after execute without status polling, pass returned `txnId`. ```typescript +import { parseUnits } from 'viem' + const tx = await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xRecipient', - value: 1n, + value: parseUnits('0.001', 18), waitForStatus: false, }) @@ -210,10 +241,12 @@ const status = await oms.wallet.getTransactionStatus({ txnId: tx.txnId }) To tune polling, pass `statusPolling`: ```typescript +import { parseUnits } from 'viem' + await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xRecipient', - value: 1n, + value: parseUnits('0.001', 18), statusPolling: { timeoutMs: 30_000, intervalMs: 1_000, @@ -227,7 +260,7 @@ available. ```typescript const tx = await oms.wallet.sendTransaction({ - network: 'polygon', + network: Networks.polygon, to: '0xTokenContract', data: '0xa9059cbb000000000000000000000000...', selectFeeOption: async (feeOptions) => { @@ -243,26 +276,27 @@ const tx = await oms.wallet.sendTransaction({ ```typescript const oms = new OMSClient({ - projectAccessKey: 'your-key', + publicApiKey: 'your-public-api-key', + projectId: 'your-project-id', environment: { walletApiUrl: 'https://staging-wallet.example.com', indexerUrlTemplate: 'https://staging-indexer.example.com/{value}', - auth: { - waasAuthScope: 'proj_1', - }, }, }) ``` +For indexer requests, `{value}` is replaced with the selected `Network.name`, such as `polygon` or `amoy`. + ### 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: ```typescript -import { MemoryStorageManager, OMSClient } from 'typescript-sdk' +import { MemoryStorageManager, OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ - projectAccessKey: 'your-key', + publicApiKey: 'your-public-api-key', + projectId: 'your-project-id', storage: new MemoryStorageManager(), }) ``` @@ -271,12 +305,19 @@ OIDC redirect auth uses separate transient storage for verifier/state data. In b ## More Examples -### Sign a Message +### Sign and Validate Message ```typescript const signature = await oms.wallet.signMessage({ - network: 'polygon', - message: '0xdeadbeef', + network: Networks.polygon, + message: 'some message to sing', +}) + +const isValid = await oms.wallet.isValidMessageSignature({ + network: Networks.polygon, + walletAddress: oms.wallet.walletAddress, + message: 'some message to sing', + signature, }) ``` @@ -284,12 +325,12 @@ const signature = await oms.wallet.signMessage({ ```typescript const signature = await oms.wallet.signTypedData({ - network: 'polygon', + network: Networks.polygon, typedData, }) const isValid = await oms.wallet.isValidTypedDataSignature({ - network: 'polygon', + network: Networks.polygon, walletAddress: oms.wallet.walletAddress, typedData, signature, @@ -299,35 +340,45 @@ const isValid = await oms.wallet.isValidTypedDataSignature({ ### Call a Contract (method string + args) ```typescript +import { parseUnits } from 'viem' + const tx = await oms.wallet.callContract({ - network: 'polygon', + network: Networks.polygon, contractAddress: '0xTokenContract', method: 'transfer(address,uint256)', args: [ { type: 'address', value: '0xRecipient' }, - { type: 'uint256', value: '1000000000000000000' }, + { type: 'uint256', value: parseUnits('1', 18).toString() }, ], }) ``` -### Query Token Balances +### Query Token and Native Balances ```typescript const { walletAddress } = oms.wallet if (!walletAddress) throw new Error('No active wallet session') const result = await oms.indexer.getTokenBalances({ - chainId: '137', - contractAddress: '0xTokenContract', + network: Networks.polygon, walletAddress, includeMetadata: true, }) for (const b of result.balances) { - console.log(b.contractAddress, b.balance) + console.log(b.contractInfo?.symbol, b.balance, b.contractInfo?.decimals) } + +const nativeBalance = await oms.indexer.getNativeTokenBalance({ + network: Networks.polygon, + walletAddress, +}) + +console.log(nativeBalance?.balance) ``` +Pass `contractAddress` to filter balances to one token contract. With `includeMetadata: true`, ERC-20 decimals are available as `contractInfo.decimals`. The response is paginated; pass `page` when requesting later pages. + ### Manage Access ```typescript @@ -344,30 +395,6 @@ for await (const page of oms.wallet.listAccessPages({ pageSize: 25 })) { await oms.wallet.revokeAccess({ targetCredentialId: grants[0].credentialId }) ``` -### Sign Out - -```typescript -await oms.wallet.signOut() -// Redirect to sign-in screen -``` - -### Handle SDK Errors - -```typescript -import { OmsSdkError } from 'typescript-sdk' - -try { - await oms.wallet.signMessage({ network: 'polygon', message: '0xdeadbeef' }) -} catch (err) { - if (err instanceof OmsSdkError) { - if (err.code === 'OMS_AUTH_COMMITMENT_CONSUMED') { - // Restart the auth flow; this OTP/OIDC commitment has already been used. - } - console.error(err.code, err.operation, err.status, err.txnId, err.retryable) - } -} -``` - ## API Reference See [API.md](./API.md) for the full method and type reference. diff --git a/examples/node/README.md b/examples/node/README.md index 69109d5..0e6f534 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, OMSClient } from 'typescript-sdk' +import { MemoryStorageManager, Networks, OMSClient } from '@0xsequence/typescript-sdk' ``` Run it from the repository root: @@ -11,7 +11,7 @@ Run it from the repository root: ```bash pnpm install pnpm build -pnpm dev:node-example +OMS_PUBLIC_API_KEY=your-public-api-key OMS_PROJECT_ID=your-project-id pnpm dev:node-example ``` The example prompts for an email address, sends an OTP code, then prompts for the code. diff --git a/examples/node/package.json b/examples/node/package.json index 46fd607..3584606 100644 --- a/examples/node/package.json +++ b/examples/node/package.json @@ -8,8 +8,7 @@ "build": "tsc --noEmit" }, "dependencies": { - "typescript-sdk": "workspace:*", - "viem": "^2.48.4" + "@0xsequence/typescript-sdk": "workspace:*" }, "devDependencies": { "@types/node": "^22.19.19", diff --git a/examples/node/signInFlow.ts b/examples/node/signInFlow.ts index ea5edc0..bfc5714 100644 --- a/examples/node/signInFlow.ts +++ b/examples/node/signInFlow.ts @@ -1,14 +1,14 @@ import readline from "node:readline/promises"; -import {MemoryStorageManager, OMSClient} from "typescript-sdk"; -import {polygonAmoy} from "viem/chains"; +import {MemoryStorageManager, Networks, OMSClient} from "@0xsequence/typescript-sdk"; -const projectAccessKey = "AQAAAAAAAAK2JvvZhWqZ51riasWBftkrVXE"; +const publicApiKey = requiredEnv("OMS_PUBLIC_API_KEY", process.env.OMS_PUBLIC_API_KEY); +const projectId = requiredEnv("OMS_PROJECT_ID", process.env.OMS_PROJECT_ID); async function main() { console.log("------------------------------------------------------------"); console.log(" OmsWallet sign-in flow"); console.log("------------------------------------------------------------"); - console.log("project access key :", mask(projectAccessKey)); + console.log("public API key :", mask(publicApiKey)); console.log(); const email = await prompt("Enter your email: "); @@ -17,13 +17,14 @@ async function main() { console.log("[setup] creating OmsWallet…"); const client = new OMSClient({ - projectAccessKey, + publicApiKey, + projectId, storage: new MemoryStorageManager(), }); console.log("[setup] ready:", client.wallet.constructor.name); console.log(); - console.log(`[step 1] signInWithEmail("${email}")`); + console.log(`[step 1] startEmailAuth("${email}")`); let t = Date.now(); try { @@ -38,7 +39,7 @@ async function main() { const code = await prompt("Enter the code from your email: "); - console.log(`[step 2] completeEmailSignIn("${mask(code)}")`); + console.log(`[step 2] completeEmailAuth("${mask(code)}")`); t = Date.now(); try { @@ -56,7 +57,7 @@ async function main() { console.log("✓ sign-in flow complete"); await client.wallet.signMessage({ - network: polygonAmoy, + network: Networks.amoy, message: "test" }); } @@ -67,6 +68,13 @@ function mask(value: string | undefined): string { return `${value.slice(0, 4)}…${value.slice(-4)}`; } +function requiredEnv(name: string, value: string | undefined): string { + if (!value) { + throw new Error(`Missing ${name}. Set it before running pnpm dev:node-example`); + } + return value; +} + async function prompt(question: string): Promise { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await rl.question(question); diff --git a/examples/react/.env.example b/examples/react/.env.example index 5304b04..0ad1393 100644 --- a/examples/react/.env.example +++ b/examples/react/.env.example @@ -1 +1,2 @@ -VITE_OMS_PROJECT_ACCESS_KEY=AQAAAAAAAAK2JvvZhWqZ51riasWBftkrVXE +VITE_OMS_PUBLIC_API_KEY=your-public-api-key +VITE_OMS_PROJECT_ID=your-oms-project-id diff --git a/examples/react/README.md b/examples/react/README.md index 03561ae..ec9c8d0 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 'typescript-sdk' +import { OMSClient } from '@0xsequence/typescript-sdk' ``` Run it from the repository root: @@ -11,6 +11,8 @@ Run it from the repository root: ```bash pnpm install pnpm build +cp examples/react/.env.example examples/react/.env.local +# Fill VITE_OMS_PUBLIC_API_KEY and VITE_OMS_PROJECT_ID in examples/react/.env.local pnpm dev:example ``` @@ -18,10 +20,11 @@ The dev server runs at `http://localhost:5173`. The deployed example is available at `https://0xsequence.github.io/typescript-sdk/react-example`. -The example includes a default demo project access key. To override it locally: +The example requires a public API key and project ID. Configure them locally before running the dev server: ```bash cp examples/react/.env.example examples/react/.env.local +# Fill VITE_OMS_PUBLIC_API_KEY and VITE_OMS_PROJECT_ID in examples/react/.env.local ``` Google/OIDC redirect sign-in uses the SDK default Google client id. diff --git a/examples/react/package.json b/examples/react/package.json index 92bf4ce..ffb2870 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -11,7 +11,7 @@ "dependencies": { "react": "^19.2.5", "react-dom": "^19.2.5", - "typescript-sdk": "workspace:*" + "@0xsequence/typescript-sdk": "workspace:*" }, "devDependencies": { "@types/react": "^19.2.14", diff --git a/examples/react/src/main.tsx b/examples/react/src/main.tsx index f9614a5..b4f43cf 100644 --- a/examples/react/src/main.tsx +++ b/examples/react/src/main.tsx @@ -1,46 +1,40 @@ import React, { useEffect, useMemo, useRef, useState } from 'react' import { createRoot } from 'react-dom/client' import { + Networks, OMSClient, + supportedNetworks, type FeeOptionSelection, type FeeOptionWithBalance, + type Network, type OMSClientSessionLoginType, -} from 'typescript-sdk' +} from '@0xsequence/typescript-sdk' import './styles.css' type Step = 'email' | 'code' | 'wallet' -type DemoNetworkId = 'polygon' | 'amoy' type FeeSelectionController = { resolve: (selection: FeeOptionSelection) => void reject: (error: Error) => void } -const NETWORKS: Array<{ - id: DemoNetworkId - label: string - explorerTxUrl: string -}> = [ - { - id: 'polygon', - label: 'Polygon', - explorerTxUrl: 'https://polygonscan.com/tx/', - }, - { - id: 'amoy', - label: 'Polygon Amoy', - explorerTxUrl: 'https://amoy.polygonscan.com/tx/', - }, -] const DEFAULT_MESSAGE = 'test' const DEFAULT_TX_TO = '0xE5E8B483FfC05967FcFed58cc98D053265af6D99' -const PROJECT_ACCESS_KEY = import.meta.env.VITE_OMS_PROJECT_ACCESS_KEY ?? 'AQAAAAAAAAK2JvvZhWqZ51riasWBftkrVXE' +const PUBLIC_API_KEY = requiredEnv('VITE_OMS_PUBLIC_API_KEY', import.meta.env.VITE_OMS_PUBLIC_API_KEY) +const PROJECT_ID = requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID) + +function requiredEnv(name: string, value: string | undefined): string { + if (!value) { + throw new Error(`Missing ${name}. Copy examples/react/.env.example to examples/react/.env.local and set it.`) + } + return value +} function App() { const [step, setStep] = useState('email') const [email, setEmail] = useState('') const [code, setCode] = useState('') const [message, setMessage] = useState(DEFAULT_MESSAGE) - const [selectedNetworkId, setSelectedNetworkId] = useState('amoy') + const [selectedNetworkId, setSelectedNetworkId] = useState(Networks.amoy.id) const [transactionTo, setTransactionTo] = useState(DEFAULT_TX_TO) const [transactionValue, setTransactionValue] = useState('0') const [walletAddress, setWalletAddress] = useState('') @@ -57,10 +51,11 @@ function App() { const oms = useMemo(() => { return new OMSClient({ - projectAccessKey: PROJECT_ACCESS_KEY, + publicApiKey: PUBLIC_API_KEY, + projectId: PROJECT_ID, }) }, []) - const selectedNetwork = NETWORKS.find(network => network.id === selectedNetworkId) ?? NETWORKS[1] + const selectedNetwork = supportedNetworks.find(network => network.id === selectedNetworkId) ?? Networks.amoy const session = oms.wallet.session useEffect(() => { @@ -144,7 +139,7 @@ function App() { async function signMessage() { await run('Signing message...', setWalletStatus, async () => { const signature = await oms.wallet.signMessage({ - network: selectedNetwork.id, + network: selectedNetwork, message, }) setLastSignature(signature) @@ -158,13 +153,13 @@ function App() { setLastTransactionExplorerUrl('') try { const tx = await oms.wallet.sendTransaction({ - network: selectedNetwork.id, + network: selectedNetwork, to: transactionTo as `0x${string}`, value: BigInt(transactionValue || '0'), selectFeeOption: waitForFeeOptionSelection, }) setLastTransactionHash(tx.txnHash ?? tx.txnId) - setLastTransactionExplorerUrl(tx.txnHash ? `${selectedNetwork.explorerTxUrl}${tx.txnHash}` : '') + setLastTransactionExplorerUrl(tx.txnHash ? transactionExplorerUrl(selectedNetwork, tx.txnHash) : '') setWalletStatus('Transaction sent.') } finally { feeSelection.current = null @@ -307,22 +302,23 @@ function App() { -
-

Network

-
- {NETWORKS.map(network => ( - - ))} +
+
+

Network

+ {selectedNetwork.nativeTokenSymbol}
+
@@ -418,6 +414,18 @@ createRoot(document.getElementById('root')!).render( , ) +function transactionExplorerUrl(network: Network, txnHash: string): string { + return `${network.explorerUrl.replace(/\/+$/, '')}/tx/${txnHash}` +} + +function networkLabel(network: Network): string { + const label = network.name + .split('-') + .map(part => part.toUpperCase() === 'BSC' ? part.toUpperCase() : part[0].toUpperCase() + part.slice(1)) + .join(' ') + return `${label} (${network.id})` +} + function formatLoginType(loginType: OMSClientSessionLoginType | undefined): string { switch (loginType) { case 'email': diff --git a/examples/react/src/styles.css b/examples/react/src/styles.css index 2cae8bb..6fe051e 100644 --- a/examples/react/src/styles.css +++ b/examples/react/src/styles.css @@ -15,7 +15,8 @@ body { } button, -input { +input, +select { font: inherit; } @@ -79,7 +80,8 @@ label { font-size: 14px; } -input { +input, +select { width: 100%; min-height: 44px; padding: 10px 12px; @@ -89,7 +91,8 @@ input { background: #ffffff; } -input:focus { +input:focus, +select:focus { outline: 2px solid #7aa7ff; outline-offset: 1px; border-color: #6b9dff; @@ -162,24 +165,32 @@ button:disabled { line-height: 1.25; } -.segmented { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 4px; - padding: 4px; - border-radius: 6px; - background: #eef2f7; +.network-tool { + gap: 8px; } -.segmented button { - min-height: 38px; - color: #344054; - background: transparent; +.tool-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; } -.segmented button[aria-checked="true"] { +.network-meta { + min-width: 48px; + padding: 4px 8px; + border-radius: 6px; color: #ffffff; background: #1d4ed8; + font-size: 12px; + font-weight: 800; + line-height: 1.2; + text-align: center; +} + +select:disabled { + color: #667085; + background: #eef2f7; } .fee-options { diff --git a/examples/react/src/vite-env.d.ts b/examples/react/src/vite-env.d.ts index 151f14d..aa51999 100644 --- a/examples/react/src/vite-env.d.ts +++ b/examples/react/src/vite-env.d.ts @@ -1,7 +1,8 @@ /// interface ImportMetaEnv { - readonly VITE_OMS_PROJECT_ACCESS_KEY?: string + readonly VITE_OMS_PUBLIC_API_KEY?: string + readonly VITE_OMS_PROJECT_ID?: string } interface ImportMeta { diff --git a/package.json b/package.json index 2a9be9d..17b8640 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,15 @@ { - "name": "typescript-sdk", - "version": "1.0.0", - "description": "", + "name": "@0xsequence/typescript-sdk", + "version": "0.1.0-alpha.0", + "description": "TypeScript SDK for OMS (Open Money Stack).", "main": "dist/index.js", "module": "dist/esm/index.js", "types": "dist/index.d.ts", + "files": [ + "dist", + "README.md", + "API.md" + ], "exports": { ".": { "types": "./dist/index.d.ts", @@ -12,10 +17,25 @@ "require": "./dist/index.js" } }, + "sideEffects": false, + "repository": { + "type": "git", + "url": "git+https://github.com/0xsequence/typescript-sdk.git" + }, + "bugs": { + "url": "https://github.com/0xsequence/typescript-sdk/issues" + }, + "homepage": "https://github.com/0xsequence/typescript-sdk#readme", + "publishConfig": { + "access": "public", + "tag": "alpha" + }, "packageManager": "pnpm@11.1.3", "scripts": { "clean": "rm -rf dist", "build": "pnpm clean && tsc && tsc -p tsconfig.esm.json && node scripts/write-esm-package.cjs", + "prepack": "pnpm build", + "prepublishOnly": "pnpm exec tsc --noEmit && pnpm test", "dev:example": "pnpm --filter react-example dev", "build:example": "pnpm --filter react-example build", "dev:node-example": "pnpm --filter node-example dev", @@ -31,6 +51,5 @@ "dotenv": "^17.4.2", "typescript": "^5.5.3", "vitest": "^4.1.5" - }, - "private": true + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93ef064..41423d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,12 +24,9 @@ importers: examples/node: dependencies: - typescript-sdk: + '@0xsequence/typescript-sdk': specifier: workspace:* version: link:../.. - viem: - specifier: ^2.48.4 - version: 2.48.4(typescript@5.9.3) devDependencies: '@types/node': specifier: ^22.19.19 @@ -43,15 +40,15 @@ importers: examples/react: dependencies: + '@0xsequence/typescript-sdk': + specifier: workspace:* + version: link:../.. react: specifier: ^19.2.5 version: 19.2.5 react-dom: specifier: ^19.2.5 version: 19.2.5(react@19.2.5) - typescript-sdk: - specifier: workspace:* - version: link:../.. devDependencies: '@types/react': specifier: ^19.2.14 diff --git a/src/clients/indexerClient.ts b/src/clients/indexerClient.ts index ba50e2e..dd8d875 100644 --- a/src/clients/indexerClient.ts +++ b/src/clients/indexerClient.ts @@ -1,8 +1,8 @@ // Converted from Swift IndexerClient. import {HttpClient} from "../httpClient.js"; -import {NetworkBindings} from "../utils/networkBindings.js"; import {errorMessage, OmsRequestError, OmsResponseError} from "../errors.js"; +import type {Network} from "../networks.js"; export interface TokenBalancesPage { page: number; @@ -10,6 +10,61 @@ export interface TokenBalancesPage { more: boolean; } +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 | null; + status?: string; +} + +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; +} + +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[]; + 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 interface TokenBalance { contractType?: string; contractAddress?: string; @@ -17,9 +72,16 @@ export interface TokenBalance { /** Wire format uses `tokenID`; this field is re-mapped during decoding. */ tokenId?: string; balance?: string; + balanceUSD?: string; + priceUSD?: string; + priceUpdatedAt?: string; blockHash?: string; blockNumber?: number; chainId?: number; + uniqueCollectibles?: string; + isSummary?: boolean; + contractInfo?: TokenContractInfo; + tokenMetadata?: TokenMetadata; } export interface TokenBalancesResult { @@ -50,9 +112,27 @@ interface TokenBalanceRaw { accountAddress?: string; tokenID?: string; // note the wire key balance?: string; + balanceUSD?: string; + priceUSD?: string; + priceUpdatedAt?: string; blockHash?: string; blockNumber?: number; chainId?: number; + uniqueCollectibles?: string; + isSummary?: boolean; + contractInfo?: TokenContractInfo; + tokenMetadata?: TokenMetadataRaw; +} + +interface TokenMetadataRaw extends Omit { + tokenId?: string; + tokenID?: string; + assets?: TokenMetadataAssetRaw[]; +} + +interface TokenMetadataAssetRaw extends Omit { + tokenId?: string; + tokenID?: string; } interface RequestPage { @@ -63,7 +143,7 @@ interface RequestPage { interface TokenBalancesRequest { page: RequestPage; - contractAddress: string; + contractAddress?: string; accountAddress: string; includeMetadata: boolean; } @@ -74,36 +154,44 @@ export interface OmsEnvironment { } export class IndexerClient { - private readonly projectAccessKey: string; + private readonly publicApiKey: string; private readonly environment: OmsEnvironment; private readonly client: HttpClient; - private readonly networks: NetworkBindings; constructor(params: { - projectAccessKey: string, + publicApiKey: string, environment: OmsEnvironment }) { - this.projectAccessKey = params.projectAccessKey; + this.publicApiKey = params.publicApiKey; this.environment = params.environment; this.client = new HttpClient(); - this.networks = new NetworkBindings(); } async getTokenBalances(params: { - chainId: string - contractAddress: string + network: Network + contractAddress?: string walletAddress: string includeMetadata: boolean + page?: { + page?: number + pageSize?: number + } }): Promise { const request: TokenBalancesRequest = { - page: { page: 0, pageSize: 40, more: false }, - contractAddress: params.contractAddress, + page: { + page: params.page?.page ?? 0, + pageSize: params.page?.pageSize ?? 40, + more: false, + }, accountAddress: params.walletAddress, includeMetadata: params.includeMetadata, }; + if (params.contractAddress) { + request.contractAddress = params.contractAddress; + } const bodyString = JSON.stringify(request); - const baseUrl = this.indexerUrl(params.chainId); + const baseUrl = this.indexerUrl(params.network); const response = await this.postJson("indexer.getTokenBalances", { baseUrl, @@ -120,11 +208,11 @@ export class IndexerClient { } async getNativeTokenBalance(params: { - chainId: string + network: Network walletAddress: string }): Promise { const response = await this.postJson("indexer.getNativeTokenBalance", { - baseUrl: this.indexerUrl(params.chainId), + baseUrl: this.indexerUrl(params.network), path: "/GetNativeTokenBalance", body: JSON.stringify({ accountAddress: params.walletAddress }), headers: this.defaultHeaders(), @@ -189,21 +277,13 @@ export class IndexerClient { return {statusCode: response.statusCode, payload}; } - private indexerUrl(chainId: string): string { - return this.environment.indexerUrlTemplate.replace("{value}", this.indexerNetworkValue(chainId)); - } - - private indexerNetworkValue(chainId: string): string { - const normalized = chainId.toLowerCase(); - if (/^\d+$/.test(normalized)) { - return this.networks.findChainNameById(BigInt(normalized)) ?? normalized; - } - return normalized; + private indexerUrl(network: Network): string { + return this.environment.indexerUrlTemplate.replace("{value}", network.name); } private defaultHeaders(): Record { return { - "X-Access-Key": this.projectAccessKey, + "X-Access-Key": this.publicApiKey, Accept: "application/json", }; } @@ -217,9 +297,31 @@ function mapTokenBalance(raw: TokenBalanceRaw): TokenBalance { accountAddress: raw.accountAddress, tokenId: raw.tokenID, balance: raw.balance, + balanceUSD: raw.balanceUSD, + priceUSD: raw.priceUSD, + priceUpdatedAt: raw.priceUpdatedAt, blockHash: raw.blockHash, blockNumber: raw.blockNumber, chainId: raw.chainId, + uniqueCollectibles: raw.uniqueCollectibles, + isSummary: raw.isSummary, + contractInfo: raw.contractInfo, + tokenMetadata: raw.tokenMetadata ? mapTokenMetadata(raw.tokenMetadata) : undefined, + }; +} + +function mapTokenMetadata(raw: TokenMetadataRaw): TokenMetadata { + const {tokenID, assets, ...metadata} = raw; + return { + ...metadata, + tokenId: raw.tokenId ?? tokenID, + assets: assets?.map(asset => { + const {tokenID: assetTokenID, ...metadataAsset} = asset; + return { + ...metadataAsset, + tokenId: asset.tokenId ?? assetTokenID, + }; + }), }; } diff --git a/src/clients/walletClient.ts b/src/clients/walletClient.ts index b8ee38d..d59e613 100644 --- a/src/clients/walletClient.ts +++ b/src/clients/walletClient.ts @@ -54,8 +54,7 @@ import { Fetch, CredentialInfo, } from '../generated/waas.gen.js' -import {NetworkBindings} from "../utils/networkBindings.js"; -import {Network} from "../types/evmTypes.js"; +import type {Network} from "../networks.js"; import { FeeOptionSelector, FeeOptionWithBalance, @@ -195,7 +194,7 @@ interface PendingOidcRedirectAuth { walletType: WalletType; redirectUri: string; issuer: string; - waasAuthScope: string; + projectId: string; } interface ResolvedOidcProvider { @@ -214,11 +213,10 @@ export class WalletClient { private readonly publicClient: WalletPublicclient private readonly storage: StorageManager private readonly redirectAuthStorage?: StorageManager - private readonly networks: NetworkBindings private readonly credentialSigner: CredentialSigner private readonly indexerClient: IndexerClient private readonly environment: Env - private readonly waasAuthScope: string + private readonly projectId: string private readonly fastTransactionStatusPollIntervalMs = 400 private readonly fastTransactionStatusPollCount = 5 private readonly transactionStatusPollIntervalMs = 2_000 @@ -236,7 +234,8 @@ export class WalletClient { private challenge = '' constructor(params: { - projectAccessKey: string, + publicApiKey: string, + projectId: string, environment: Env, storage?: StorageManager redirectAuthStorage?: StorageManager @@ -246,7 +245,7 @@ export class WalletClient { this.storage = params.storage ?? createDefaultStorage() this.redirectAuthStorage = params.redirectAuthStorage ?? defaultRedirectAuthStorage() this.credentialSigner = params.credentialSigner ?? new WebCryptoP256CredentialSigner() - this.waasAuthScope = params.environment.auth?.waasAuthScope ?? Constants.defaultWaasAuthScope + this.projectId = params.projectId const storedId = this.storage.get(Constants.walletIdStorageKey) const storedAddress = this.storage.get(Constants.walletAddressStorageKey) @@ -265,17 +264,16 @@ export class WalletClient { this.sessionEmail = undefined } - const signedFetch = createSignedFetch(params.projectAccessKey, this.credentialSigner, this.waasAuthScope) + const signedFetch = createSignedFetch(params.publicApiKey, this.credentialSigner, this.projectId) this.client = new Walletclient(params.environment.walletApiUrl, signedFetch) this.publicClient = new WalletPublicclient( params.environment.walletApiUrl, - createAccessKeyFetch(params.projectAccessKey), + createAccessKeyFetch(params.publicApiKey), ) this.indexerClient = new IndexerClient({ - projectAccessKey: params.projectAccessKey, + publicApiKey: params.publicApiKey, environment: params.environment, }) - this.networks = new NetworkBindings() } /** Durable metadata for the completed wallet session. */ @@ -374,7 +372,7 @@ export class WalletClient { const nonce = generateOidcNonce() const state = encodeOidcState({ nonce, - scope: this.waasAuthScope, + scope: this.projectId, ...(oauthRedirectUri !== params.redirectUri ? {redirect_uri: params.redirectUri} : {}), }) @@ -385,7 +383,7 @@ export class WalletClient { walletType: params.walletType ?? WalletType.Ethereum, redirectUri: params.redirectUri, issuer: provider.config.issuer, - waasAuthScope: this.waasAuthScope, + projectId: this.projectId, }) const authorizeParams = { @@ -550,7 +548,7 @@ export class WalletClient { return this.runOperation("wallet.signMessage", async () => { await this.requireActiveSession("wallet.signMessage") const request: SignMessageRequest = { - network: this.parseWalletNetwork(params.network), + network: params.network.id.toString(), walletId: this.walletId, message: params.message, } @@ -563,7 +561,7 @@ export class WalletClient { return this.runOperation("wallet.signTypedData", async () => { await this.requireActiveSession("wallet.signTypedData") const request: SignTypedDataRequest = { - network: this.parseWalletNetwork(params.network), + network: params.network.id.toString(), walletId: this.walletId, typedData: normalizeJsonBigInts(params.typedData), } @@ -575,7 +573,7 @@ export class WalletClient { async isValidMessageSignature(params: IsValidMessageSignatureParams): Promise { return this.runOperation("wallet.isValidMessageSignature", async () => { const request: IsValidMessageSignatureRequest = { - network: params.network === undefined ? undefined : this.parseWalletNetwork(params.network), + network: params.network?.id.toString(), walletAddress: params.walletAddress, walletId: params.walletId ?? (params.walletAddress ? undefined : this.activeWalletId()), message: params.message, @@ -589,7 +587,7 @@ export class WalletClient { async isValidTypedDataSignature(params: IsValidTypedDataSignatureParams): Promise { return this.runOperation("wallet.isValidTypedDataSignature", async () => { const request: IsValidTypedDataSignatureRequest = { - network: params.network === undefined ? undefined : this.parseWalletNetwork(params.network), + network: params.network?.id.toString(), walletAddress: params.walletAddress, walletId: params.walletId ?? (params.walletAddress ? undefined : this.activeWalletId()), typedData: normalizeJsonBigInts(params.typedData), @@ -615,7 +613,7 @@ export class WalletClient { : params.data const request: PrepareEthereumTransactionRequest = { - network: this.parseWalletNetwork(params.network), + network: params.network.id.toString(), walletId: this.walletId, to: params.to, value: (params.value ?? 0n).toString(), @@ -647,7 +645,7 @@ export class WalletClient { return this.runOperation("wallet.callContract", async () => { await this.requireActiveSession("wallet.callContract") const request: PrepareEthereumContractCallRequest = { - network: this.parseWalletNetwork(params.network), + network: params.network.id.toString(), walletId: this.walletId, contract: params.contractAddress, method: params.method, @@ -941,7 +939,7 @@ export class WalletClient { typeof parsed.nonce !== 'string' || typeof parsed.redirectUri !== 'string' || typeof parsed.issuer !== 'string' || - typeof parsed.waasAuthScope !== 'string' + typeof parsed.projectId !== 'string' ) { throw new Error('Pending OIDC redirect auth is invalid') } @@ -953,7 +951,7 @@ export class WalletClient { walletType: isWalletType(parsed.walletType) ? parsed.walletType : WalletType.Ethereum, redirectUri: parsed.redirectUri, issuer: parsed.issuer, - waasAuthScope: parsed.waasAuthScope, + projectId: parsed.projectId, } } catch (error) { throw error instanceof Error ? error : new Error('Pending OIDC redirect auth is invalid') @@ -965,7 +963,7 @@ export class WalletClient { if (state.nonce !== pending.nonce) { throw new Error('OIDC state nonce mismatch') } - if (state.scope !== pending.waasAuthScope) { + if (state.scope !== pending.projectId) { throw new Error('OIDC state scope mismatch') } if (state.redirect_uri !== undefined && state.redirect_uri !== pending.redirectUri) { @@ -1116,7 +1114,7 @@ export class WalletClient { walletAddress: Address, ): Promise { return this.indexerClient.getNativeTokenBalance({ - chainId: this.parseIndexerNetwork(network), + network, walletAddress, }).catch(() => undefined) } @@ -1127,7 +1125,7 @@ export class WalletClient { walletAddress: Address, ): Promise { return this.indexerClient.getTokenBalances({ - chainId: this.parseIndexerNetwork(network), + network, contractAddress, walletAddress, includeMetadata: false, @@ -1141,7 +1139,7 @@ export class WalletClient { balance: '0', blockHash: undefined, blockNumber: undefined, - chainId: Number(this.parseWalletNetwork(network)), + chainId: network.id, }).catch(() => undefined) } @@ -1211,32 +1209,6 @@ export class WalletClient { return this.walletId || undefined } - private parseWalletNetwork(network: Network): string { - if (typeof network === 'string') { - const normalized = network.toLowerCase() - return this.networks.getChainIdByName(normalized)?.toString() ?? normalized - } else if (typeof network === 'bigint') { - return network.toString() - } else { - return BigInt(network.id).toString() - } - } - - private parseIndexerNetwork(network: Network): string { - if (typeof network === 'string') { - const normalized = network.toLowerCase() - if (/^\d+$/.test(normalized)) { - return this.networks.findChainNameById(BigInt(normalized)) ?? normalized - } - return normalized - } else if (typeof network === 'bigint') { - return this.networks.findChainNameById(network) ?? network.toString() - } else { - const chainId = BigInt(network.id) - return this.networks.findChainNameById(chainId) ?? chainId.toString() - } - } - private isNativeToken(feeOption: FeeOption): boolean { return feeOption.token.type.toLowerCase() === 'native' || (!feeOption.token.contractAddress && !feeOption.token.tokenID) @@ -1285,12 +1257,12 @@ function defaultRedirectAuthStorage(): StorageManager | undefined { : undefined } -function createAccessKeyFetch(projectAccessKey: string): Fetch { +function createAccessKeyFetch(publicApiKey: string): Fetch { return async (input: RequestInfo, init?: RequestInit): Promise => { const existingHeaders = (init?.headers ?? {}) as Record const headers: Record = { ...existingHeaders, - 'X-Access-Key': projectAccessKey, + 'X-Access-Key': publicApiKey, } return globalThis.fetch(input, {...init, headers}) diff --git a/src/index.ts b/src/index.ts index ef35867..f8ed44b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,13 @@ export { createDefaultStorage, type StorageManager, } from './storageManager.js' +export { + Networks, + findNetworkById, + findNetworkByName, + supportedNetworks, + type Network, +} from './networks.js' export { TransactionMode, TransactionStatus, @@ -63,6 +70,14 @@ export type { StartOidcRedirectAuthResult, WalletActivationResult, } from './clients/walletClient.js' +export type { + TokenContractInfo, + TokenBalance, + TokenBalancesPage, + TokenBalancesResult, + TokenMetadata, + TokenMetadataAsset, +} from './clients/indexerClient.js' export type { AccessGrant, AccessGrantPage, diff --git a/src/networks.ts b/src/networks.ts new file mode 100644 index 0000000..2471957 --- /dev/null +++ b/src/networks.ts @@ -0,0 +1,118 @@ +export interface Network { + readonly id: number + readonly name: string + readonly nativeTokenSymbol: string + readonly explorerUrl: string +} + +export const Networks = { + mainnet: { + id: 1, + name: 'mainnet', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://etherscan.io', + }, + sepolia: { + id: 11155111, + name: 'sepolia', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://sepolia.etherscan.io', + }, + polygon: { + id: 137, + name: 'polygon', + nativeTokenSymbol: 'POL', + explorerUrl: 'https://polygonscan.com', + }, + amoy: { + id: 80002, + name: 'amoy', + nativeTokenSymbol: 'POL', + explorerUrl: 'https://amoy.polygonscan.com', + }, + arbitrum: { + id: 42161, + name: 'arbitrum', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://arbiscan.io', + }, + arbitrumSepolia: { + id: 421614, + name: 'arbitrum-sepolia', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://sepolia.arbiscan.io', + }, + optimism: { + id: 10, + name: 'optimism', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://optimistic.etherscan.io', + }, + optimismSepolia: { + id: 11155420, + name: 'optimism-sepolia', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://sepolia-optimism.etherscan.io', + }, + base: { + id: 8453, + name: 'base', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://basescan.org', + }, + baseSepolia: { + id: 84532, + name: 'base-sepolia', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://sepolia.basescan.org', + }, + bsc: { + id: 56, + name: 'bsc', + nativeTokenSymbol: 'BNB', + explorerUrl: 'https://bscscan.com', + }, + bscTestnet: { + id: 97, + name: 'bsc-testnet', + nativeTokenSymbol: 'BNB', + explorerUrl: 'https://testnet.bscscan.com', + }, + arbitrumNova: { + id: 42170, + name: 'arbitrum-nova', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://nova.arbiscan.io', + }, + avalanche: { + id: 43114, + name: 'avalanche', + nativeTokenSymbol: 'AVAX', + explorerUrl: 'https://subnets.avax.network/c-chain', + }, + avalancheTestnet: { + id: 43113, + name: 'avalanche-testnet', + nativeTokenSymbol: 'AVAX', + explorerUrl: 'https://subnets-test.avax.network/c-chain', + }, + katana: { + id: 747474, + name: 'katana', + nativeTokenSymbol: 'ETH', + explorerUrl: 'https://katanascan.com', + }, +} as const satisfies Record; + +export const supportedNetworks: readonly Network[] = Object.freeze(Object.values(Networks)); + +const networksById = new Map(supportedNetworks.map(network => [network.id, network])); +const networksByName = new Map(supportedNetworks.map(network => [network.name.toLowerCase(), network])); + +export function findNetworkById(chainId: number): Network | undefined { + return networksById.get(chainId); +} + +export function findNetworkByName(name: string): Network | undefined { + return networksByName.get(name.trim().toLowerCase()); +} diff --git a/src/omsClient.ts b/src/omsClient.ts index 232169e..8e59f12 100644 --- a/src/omsClient.ts +++ b/src/omsClient.ts @@ -3,9 +3,11 @@ import {defaultOmsEnvironment, OmsEnvironment} from "./omsEnvironment.js"; import {createDefaultStorage, StorageManager} from "./storageManager.js"; import {IndexerClient} from "./clients/indexerClient.js"; import type {CredentialSigner} from "./credentialSigner.js"; +import {supportedNetworks} from "./networks.js"; interface OMSClientBaseParams { - projectAccessKey: string; + publicApiKey: string; + projectId: string; storage?: StorageManager; redirectAuthStorage?: StorageManager; credentialSigner?: CredentialSigner; @@ -19,13 +21,15 @@ type DefaultOMSClientParams = OMSClientBaseParams & {environment?: undefined}; class OMSClientImpl { public readonly wallet: WalletClient; public readonly indexer: IndexerClient; + public readonly supportedNetworks = supportedNetworks; constructor(params: OMSClientBaseParams & {environment?: Env}) { const environment = (params.environment ?? defaultOmsEnvironment) as Env; const storage = params.storage ?? createDefaultStorage() this.wallet = new WalletClient({ - projectAccessKey: params.projectAccessKey, + publicApiKey: params.publicApiKey, + projectId: params.projectId, environment, storage, redirectAuthStorage: params.redirectAuthStorage, @@ -33,11 +37,10 @@ class OMSClientImpl { }); this.indexer = new IndexerClient({ - projectAccessKey: params.projectAccessKey, + publicApiKey: params.publicApiKey, environment }); } - } export type OMSClient = OMSClientImpl; diff --git a/src/omsEnvironment.ts b/src/omsEnvironment.ts index 907e38a..85eaff2 100644 --- a/src/omsEnvironment.ts +++ b/src/omsEnvironment.ts @@ -12,7 +12,6 @@ export interface OidcProviderConfig { export interface OmsAuthConfig< OidcProviders extends Record = Record, > { - waasAuthScope?: string; oidcProviders?: OidcProviders; } @@ -25,7 +24,7 @@ export interface OmsEnvironment< } export const defaultOmsEnvironment = { - walletApiUrl: "https://d1sctl7y41hot5.cloudfront.net", + walletApiUrl: "https://d26giflyqapd29.cloudfront.net", indexerUrlTemplate: "https://dev-{value}-indexer.sequence.app/rpc/Indexer/", auth: { oidcProviders: { diff --git a/src/signedFetch.ts b/src/signedFetch.ts index f5cad53..b901741 100644 --- a/src/signedFetch.ts +++ b/src/signedFetch.ts @@ -1,5 +1,4 @@ import {Fetch} from "./generated/waas.gen.js"; -import {Constants} from "./utils/constants.js"; import {RequestUtils} from "./utils/requestUtils.js"; import type {CredentialSigner} from "./credentialSigner.js"; @@ -7,19 +6,19 @@ async function buildWalletSignatureHeader( endpoint: string, signer: CredentialSigner, payload: string, - waasAuthScope: string, + projectId: string, ): Promise { const credentialId = await signer.credentialId() const nonce = await signer.nextNonce() - const preimage = RequestUtils.buildWalletRequestPreimage(endpoint, nonce, waasAuthScope, payload) + const preimage = RequestUtils.buildWalletRequestPreimage(endpoint, nonce, projectId, payload) const signature = await signer.sign(preimage) - return RequestUtils.buildWalletSignatureHeader(signer.signingAlgorithm, waasAuthScope, credentialId, nonce, signature) + return RequestUtils.buildWalletSignatureHeader(signer.signingAlgorithm, projectId, credentialId, nonce, signature) } export function createSignedFetch( - projectAccessKey: string, + publicApiKey: string, signer: CredentialSigner, - waasAuthScope: string = Constants.defaultWaasAuthScope, + projectId: string, ): Fetch { return async (input: RequestInfo, init?: RequestInit): Promise => { const url = typeof input === 'string' ? input : (input as Request).url @@ -28,12 +27,12 @@ export function createSignedFetch( const body = typeof init?.body === 'string' ? init.body : '' - const signatureHeader = await buildWalletSignatureHeader(endpoint, signer, body, waasAuthScope) + const signatureHeader = await buildWalletSignatureHeader(endpoint, signer, body, projectId) const existingHeaders = (init?.headers ?? {}) as Record const headers: Record = { ...existingHeaders, - 'X-Access-Key': projectAccessKey, + 'X-Access-Key': publicApiKey, 'OMS-Wallet-Signature': signatureHeader, } diff --git a/src/types/evmTypes.ts b/src/types/evmTypes.ts index 5dfa310..5ec00e1 100644 --- a/src/types/evmTypes.ts +++ b/src/types/evmTypes.ts @@ -1,3 +1 @@ -import {Chain} from "viem"; - -export type Network = string | bigint | Chain; \ No newline at end of file +export type {Network} from "../networks.js"; diff --git a/src/types/transactionTypes.ts b/src/types/transactionTypes.ts index 71a41a8..9ee1db5 100644 --- a/src/types/transactionTypes.ts +++ b/src/types/transactionTypes.ts @@ -1,5 +1,5 @@ -import {Abi, Address, Chain, ContractFunctionName, EncodeFunctionDataParameters, Hex} from "viem"; -import {Network} from "./evmTypes.js"; +import {Abi, Address, ContractFunctionName, EncodeFunctionDataParameters, Hex} from "viem"; +import type {Network} from "../networks.js"; import type { FeeOption, FeeOptionSelection, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index d0c116a..4ac9747 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -4,6 +4,5 @@ export const Constants = { sessionExpiresAtStorageKey: 'omsWallet_session_expires_at', sessionLoginTypeStorageKey: 'omsWallet_session_login_type', sessionEmailStorageKey: 'omsWallet_session_email', - defaultWaasAuthScope: 'proj_1', redirectAuthStorageKey: 'omsWallet_oidc_redirect_auth', } as const diff --git a/src/utils/networkBindings.ts b/src/utils/networkBindings.ts deleted file mode 100644 index 2d5e478..0000000 --- a/src/utils/networkBindings.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface Network { - id: bigint - name: string -} - -export class NetworkBindings { - private readonly byId: ReadonlyMap - private readonly byName: ReadonlyMap - - constructor(networks: readonly Network[] = NetworkBindings.DEFAULT_NETWORKS) { - this.byId = new Map(networks.map(n => [n.id, n.name])) - this.byName = new Map(networks.map(n => [n.name.toLowerCase(), n.id])) - } - - findChainNameById(id: bigint): string | undefined { - return this.byId.get(id) - } - - getChainNameById(id: bigint): string { - return this.findChainNameById(id) ?? 'undefined' - } - - getChainIdByName(name: string): bigint | undefined { - return this.byName.get(name.toLowerCase()) - } - - static readonly DEFAULT_NETWORKS: readonly Network[] = [ - { id: BigInt(137), name: 'polygon' }, - { id: BigInt(80002), name: 'amoy' }, - ] -} diff --git a/tests/credentialSigner.test.ts b/tests/credentialSigner.test.ts index 77764f2..cc30f54 100644 --- a/tests/credentialSigner.test.ts +++ b/tests/credentialSigner.test.ts @@ -1,30 +1,31 @@ import {describe, expect, it} from "vitest"; -import {Constants} from "../src/utils/constants"; import {RequestUtils} from "../src/utils/requestUtils"; describe("RequestUtils", () => { it("builds wallet auth request vectors", () => { + const projectId = "project-id"; + expect(RequestUtils.buildWalletRequestPreimage( "/CommitVerifier", "42", - Constants.defaultWaasAuthScope, + projectId, "{\"walletId\":\"wallet-id\"}", )).toBe( "POST /rpc/Wallet/CommitVerifier\n" + "nonce: 42\n" + - `scope: ${Constants.defaultWaasAuthScope}\n\n` + + `scope: ${projectId}\n\n` + "{\"walletId\":\"wallet-id\"}", ); expect(RequestUtils.buildWalletSignatureHeader( "ecdsa-p256-sha256", - Constants.defaultWaasAuthScope, + projectId, `0x04${"11".repeat(64)}`, "42", `0x${"22".repeat(64)}`, )).toBe( - `alg="ecdsa-p256-sha256", scope="${Constants.defaultWaasAuthScope}", cred="0x04${"11".repeat(64)}", nonce=42, sig="0x${"22".repeat(64)}"`, + `alg="ecdsa-p256-sha256", scope="${projectId}", cred="0x04${"11".repeat(64)}", nonce=42, sig="0x${"22".repeat(64)}"`, ); }); }); diff --git a/tests/indexerClient.test.ts b/tests/indexerClient.test.ts index 4526d0f..24d31c5 100644 --- a/tests/indexerClient.test.ts +++ b/tests/indexerClient.test.ts @@ -1,23 +1,79 @@ import {afterEach, describe, expect, it, vi} from "vitest"; import {IndexerClient} from "../src/clients/indexerClient"; +import {Networks} from "../src/networks"; afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); }); -describe("IndexerClient errors", () => { +describe("IndexerClient", () => { + it("omits contractAddress when querying balances across contracts", async () => { + const fetchMock = vi.fn(async () => new Response(JSON.stringify({ + page: {page: 1, pageSize: 25, more: false}, + balances: [{ + contractType: "ERC20", + contractAddress: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + accountAddress: "0x9999999999999999999999999999999999999999", + tokenID: "0", + balance: "141799", + balanceUSD: "0.141799", + priceUSD: "1", + chainId: 137, + contractInfo: { + name: "USDC", + symbol: "USDC", + decimals: 6, + }, + }], + }), {status: 200})); + vi.stubGlobal("fetch", fetchMock); + + const indexer = new IndexerClient({ + publicApiKey: "public-api-key", + environment: testEnvironment(), + }); + + await expect(indexer.getTokenBalances({ + network: Networks.polygon, + walletAddress: "0x9999999999999999999999999999999999999999", + includeMetadata: true, + page: {page: 1, pageSize: 25}, + })).resolves.toMatchObject({ + status: 200, + page: {page: 1, pageSize: 25, more: false}, + balances: [{ + tokenId: "0", + balance: "141799", + balanceUSD: "0.141799", + priceUSD: "1", + contractInfo: { + symbol: "USDC", + decimals: 6, + }, + }], + }); + + expect(fetchMock.mock.calls[0][0].toString()).toBe("https://indexer.example/polygon/GetTokenBalances"); + expect(JSON.parse(fetchMock.mock.calls[0][1]?.body as string)).toEqual({ + page: {page: 1, pageSize: 25, more: false}, + accountAddress: "0x9999999999999999999999999999999999999999", + includeMetadata: true, + }); + }); + it("wraps invalid JSON responses in typed SDK errors", async () => { - vi.stubGlobal("fetch", vi.fn(async () => new Response("not-json", {status: 200}))); + const fetchMock = vi.fn(async () => new Response("not-json", {status: 200})); + vi.stubGlobal("fetch", fetchMock); const indexer = new IndexerClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", environment: testEnvironment(), }); await expect(indexer.getTokenBalances({ - chainId: "137", + network: Networks.polygon, contractAddress: "0x2222222222222222222222222222222222222222", walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, @@ -26,18 +82,19 @@ describe("IndexerClient errors", () => { operation: "indexer.getTokenBalances", status: 200, }); + expect(fetchMock.mock.calls[0][0].toString()).toBe("https://indexer.example/polygon/GetTokenBalances"); }); it("wraps non-JSON HTTP responses as retryable HTTP errors", async () => { vi.stubGlobal("fetch", vi.fn(async () => new Response("Bad Gateway", {status: 502}))); const indexer = new IndexerClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", environment: testEnvironment(), }); await expect(indexer.getTokenBalances({ - chainId: "137", + network: Networks.polygon, contractAddress: "0x2222222222222222222222222222222222222222", walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, diff --git a/tests/networks.test.ts b/tests/networks.test.ts new file mode 100644 index 0000000..1fae63c --- /dev/null +++ b/tests/networks.test.ts @@ -0,0 +1,53 @@ +import {describe, expect, it} from "vitest"; + +import { + Networks, + OMSClient, + findNetworkById, + findNetworkByName, + supportedNetworks, +} from "../src"; + +describe("Networks", () => { + it("exposes the supported network registry", () => { + expect(supportedNetworks).toEqual([ + Networks.mainnet, + Networks.sepolia, + Networks.polygon, + Networks.amoy, + Networks.arbitrum, + Networks.arbitrumSepolia, + Networks.optimism, + Networks.optimismSepolia, + Networks.base, + Networks.baseSepolia, + Networks.bsc, + Networks.bscTestnet, + Networks.arbitrumNova, + Networks.avalanche, + Networks.avalancheTestnet, + Networks.katana, + ]); + expect(Networks.katana).toEqual({ + id: 747474, + name: "katana", + nativeTokenSymbol: "ETH", + explorerUrl: "https://katanascan.com", + }); + }); + + it("looks up networks by id or name", () => { + expect(findNetworkById(43113)).toBe(Networks.avalancheTestnet); + expect(findNetworkById(421614)).toBe(Networks.arbitrumSepolia); + expect(findNetworkByName("base-sepolia")).toBe(Networks.baseSepolia); + }); + + it("is available from OMSClient", () => { + const oms = new OMSClient({ + publicApiKey: "public-api-key", + projectId: "project-id", + }); + + expect(oms.supportedNetworks).toBe(supportedNetworks); + }); +}); diff --git a/tests/oidcRedirectAuth.test.ts b/tests/oidcRedirectAuth.test.ts index 6035297..3c0a31c 100644 --- a/tests/oidcRedirectAuth.test.ts +++ b/tests/oidcRedirectAuth.test.ts @@ -81,7 +81,7 @@ describe("WalletClient OIDC redirect auth", () => { expect(authorizeUrl.searchParams.get("login_hint")).toBe("user@example.com"); const state = decodeOidcState(result.state); - expect(state.scope).toBe(Constants.defaultWaasAuthScope); + expect(state.scope).toBe("project-id"); expect(state.redirect_uri).toBe("https://app.example/auth/callback"); expect(redirectAuthStorage.get(Constants.redirectAuthStorageKey)).toContain("verifier-1"); }); @@ -114,7 +114,7 @@ describe("WalletClient OIDC redirect auth", () => { expect(state.redirect_uri).toBe("http://localhost:5173/auth/callback"); }); - it("uses provider relay defaults and custom WaaS auth scope in headers and state", async () => { + it("uses provider relay defaults and project ID in headers and state", async () => { const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => { const headers = init?.headers as Record; expect(headers["OMS-Wallet-Signature"]).toContain('scope="proj_custom"'); @@ -132,11 +132,11 @@ describe("WalletClient OIDC redirect auth", () => { const wallet = createWalletClient({ redirectAuthStorage: new MemoryStorageManager(), credentialSigner: signer, + projectId: "proj_custom", environment: defineOmsEnvironment({ walletApiUrl: "https://wallet.example", indexerUrlTemplate: "https://indexer.example/{value}", auth: { - waasAuthScope: "proj_custom", oidcProviders: { google: googleOidcProvider({ clientId: "google-client", @@ -383,7 +383,7 @@ describe("WalletClient OIDC redirect auth", () => { }); const badState = encodeOidcState({ nonce: "bad-nonce", - scope: Constants.defaultWaasAuthScope, + scope: "project-id", }); await expect(wallet.completeOidcRedirectAuth({ @@ -448,7 +448,7 @@ describe("WalletClient OIDC redirect auth", () => { }); const state = encodeOidcState({ nonce: "nonce-1", - scope: Constants.defaultWaasAuthScope, + scope: "project-id", }); await expect(wallet.completeOidcRedirectAuth({ @@ -536,10 +536,12 @@ function createWalletClient { const environment = params.environment ?? testEnvironment() as Env; return new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: params.projectId ?? "project-id", environment, storage: new MemoryStorageManager(), redirectAuthStorage: params.redirectAuthStorage, diff --git a/tests/walletAccess.test.ts b/tests/walletAccess.test.ts index 2579e43..33004ba 100644 --- a/tests/walletAccess.test.ts +++ b/tests/walletAccess.test.ts @@ -93,7 +93,8 @@ describe("WalletClient access management", () => { function createWalletWithSession(): WalletClient { const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), credentialSigner: new MockSigner(), diff --git a/tests/walletErrors.test.ts b/tests/walletErrors.test.ts index 7c5473a..9a85076 100644 --- a/tests/walletErrors.test.ts +++ b/tests/walletErrors.test.ts @@ -32,7 +32,8 @@ afterEach(() => { describe("WalletClient errors", () => { it("wraps local validation failures separately from request failures", async () => { const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), redirectAuthStorage: new MemoryStorageManager(), @@ -52,7 +53,8 @@ describe("WalletClient errors", () => { vi.stubGlobal("fetch", vi.fn(async () => new Response("Bad Gateway", {status: 502}))); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), credentialSigner: new MockSigner(), @@ -82,7 +84,8 @@ describe("WalletClient errors", () => { })); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), credentialSigner: new MockSigner(), diff --git a/tests/walletSession.test.ts b/tests/walletSession.test.ts index 45268d2..aa92521 100644 --- a/tests/walletSession.test.ts +++ b/tests/walletSession.test.ts @@ -2,6 +2,7 @@ import {afterEach, describe, expect, it, vi} from "vitest"; import {WalletClient} from "../src/clients/walletClient"; import type {CredentialSigner} from "../src/credentialSigner"; +import {Networks} from "../src/networks"; import {OMSClient} from "../src/omsClient"; import {MemoryStorageManager} from "../src/storageManager"; import {Constants} from "../src/utils/constants"; @@ -40,7 +41,8 @@ describe("WalletClient session storage", () => { vi.stubGlobal("localStorage", undefined); const client = new OMSClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), credentialSigner: new MockSigner(), }); @@ -57,13 +59,14 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage, credentialSigner: new MockSigner(false), }); - await expect(wallet.signMessage({network: "polygon", message: "hello"})).rejects.toMatchObject({ + await expect(wallet.signMessage({network: Networks.polygon, message: "hello"})).rejects.toMatchObject({ code: "OMS_SESSION_MISSING", operation: "wallet.signMessage", message: "No active wallet session", @@ -81,7 +84,8 @@ describe("WalletClient session storage", () => { const redirectAuthStorage = new MemoryStorageManager(); redirectAuthStorage.set(Constants.redirectAuthStorageKey, JSON.stringify({verifier: "old-verifier"})); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage, redirectAuthStorage, @@ -138,7 +142,8 @@ describe("WalletClient session storage", () => { vi.stubGlobal("fetch", fetchMock); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage, redirectAuthStorage, @@ -166,7 +171,8 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage, credentialSigner: new MockSigner(), @@ -221,7 +227,8 @@ describe("WalletClient session storage", () => { vi.stubGlobal("fetch", fetchMock); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage, credentialSigner: new MockSigner(), @@ -293,7 +300,8 @@ describe("WalletClient session storage", () => { vi.stubGlobal("fetch", fetchMock); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), credentialSigner: new MockSigner(), @@ -348,7 +356,8 @@ describe("WalletClient session storage", () => { const storage = new MemoryStorageManager(); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage, credentialSigner: new MockSigner(), @@ -397,7 +406,8 @@ describe("WalletClient session storage", () => { vi.stubGlobal("fetch", fetchMock); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), credentialSigner: new MockSigner(), diff --git a/tests/walletSigning.test.ts b/tests/walletSigning.test.ts index 54c5a56..da75979 100644 --- a/tests/walletSigning.test.ts +++ b/tests/walletSigning.test.ts @@ -2,6 +2,7 @@ import {afterEach, describe, expect, it, vi} from "vitest"; import {WalletClient} from "../src/clients/walletClient"; import type {CredentialSigner} from "../src/credentialSigner"; +import {Networks} from "../src/networks"; import {MemoryStorageManager} from "../src/storageManager"; class MockSigner implements CredentialSigner { @@ -63,7 +64,7 @@ describe("WalletClient signing", () => { const url = input.toString(); const body = JSON.parse(init?.body as string); - expect((init?.headers as Record)["X-Access-Key"]).toBe("project-key"); + expect((init?.headers as Record)["X-Access-Key"]).toBe("public-api-key"); const headers = init?.headers as Record; expect(headers["OMS-Wallet-Signature"]).toContain('alg="ecdsa-p256-sha256"'); expect(headers.Authorization).toBeUndefined(); @@ -83,7 +84,7 @@ describe("WalletClient signing", () => { const wallet = createWalletWithSession("0x1111111111111111111111111111111111111111"); - await expect(wallet.signTypedData({network: "polygon", typedData})).resolves.toBe("0xsigned"); + await expect(wallet.signTypedData({network: Networks.polygon, typedData})).resolves.toBe("0xsigned"); }); it("validates signatures through the generated wallet public client", async () => { @@ -104,7 +105,7 @@ describe("WalletClient signing", () => { const body = JSON.parse(init?.body as string); const headers = init?.headers as Record; - expect(headers["X-Access-Key"]).toBe("project-key"); + expect(headers["X-Access-Key"]).toBe("public-api-key"); expect(headers["OMS-Wallet-Signature"]).toBeUndefined(); expect(headers.Authorization).toBeUndefined(); @@ -135,13 +136,13 @@ describe("WalletClient signing", () => { const wallet = createWalletWithSession("0x1111111111111111111111111111111111111111"); await expect(wallet.isValidMessageSignature({ - network: "polygon", + network: Networks.polygon, message: "hello", signature: "0xmessage", })).resolves.toBe(true); await expect(wallet.isValidTypedDataSignature({ - network: 137n, + network: Networks.polygon, walletAddress: "0x1111111111111111111111111111111111111111", typedData, signature: "0xtyped", @@ -151,7 +152,8 @@ describe("WalletClient signing", () => { function createWalletWithSession(walletAddress: string): WalletClient { const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), credentialSigner: new MockSigner(), diff --git a/tests/walletTransactions.test.ts b/tests/walletTransactions.test.ts index a226b5e..6b4e985 100644 --- a/tests/walletTransactions.test.ts +++ b/tests/walletTransactions.test.ts @@ -3,6 +3,7 @@ import {afterEach, describe, expect, it, vi} from "vitest"; import {WalletClient} from "../src/clients/walletClient"; import type {CredentialSigner} from "../src/credentialSigner"; import {TransactionStatus} from "../src/generated/waas.gen"; +import {Networks} from "../src/networks"; import {MemoryStorageManager} from "../src/storageManager"; class MockSigner implements CredentialSigner { @@ -138,7 +139,7 @@ describe("WalletClient transactions", () => { ); const response = await wallet.sendTransaction({ - network: "polygon", + network: Networks.polygon, to: "0x1111111111111111111111111111111111111111", value: 0n, selectFeeOption: (feeOptions) => { @@ -187,7 +188,7 @@ describe("WalletClient transactions", () => { ); const response = await wallet.sendTransaction({ - network: "polygon", + network: Networks.polygon, to: "0x1111111111111111111111111111111111111111", value: 0n, waitForStatus: false, @@ -217,7 +218,8 @@ describe("WalletClient transactions", () => { vi.stubGlobal("fetch", fetchMock); const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), credentialSigner: new MockSigner(), @@ -261,7 +263,7 @@ describe("WalletClient transactions", () => { ); await expect(wallet.sendTransaction({ - network: "polygon", + network: Networks.polygon, to: "0x1111111111111111111111111111111111111111", value: 0n, })).rejects.toMatchObject({ @@ -275,7 +277,8 @@ describe("WalletClient transactions", () => { function createWalletWithSession(storage: MemoryStorageManager, walletAddress: string): WalletClient { const wallet = new WalletClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: testEnvironment(), storage, credentialSigner: new MockSigner(), diff --git a/type-tests/oidcProviderTypes.ts b/type-tests/oidcProviderTypes.ts index de257ea..f35e373 100644 --- a/type-tests/oidcProviderTypes.ts +++ b/type-tests/oidcProviderTypes.ts @@ -1,5 +1,19 @@ import {WalletClient, type OidcProviderName} from "../src/clients/walletClient"; -import {OMSClient, type OMSClientSessionLoginType, type OMSClientSessionState} from "../src/index"; +import { + Networks, + OMSClient, + findNetworkById, + findNetworkByName, + supportedNetworks, + type Network, + type OMSClientSessionLoginType, + type OMSClientSessionState, + type TokenBalance, + type TokenBalancesPage, + type TokenBalancesResult, + type TokenContractInfo, + type TokenMetadata, +} from "../src/index"; import {defineOmsEnvironment, type OmsEnvironment} from "../src/omsEnvironment"; import {googleOidcProvider} from "../src/oidc"; @@ -47,11 +61,87 @@ if (false) { }); } -const defaultClient = new OMSClient({projectAccessKey: "project-key"}); +const defaultClient = new OMSClient({ + publicApiKey: "public-api-key", + projectId: "project-id", +}); +// @ts-expect-error publicApiKey is required. +new OMSClient({projectId: "project-id"}); +// @ts-expect-error projectId is required. +new OMSClient({publicApiKey: "public-api-key"}); +// @ts-expect-error old projectAccessKey initializer name is not supported. +new OMSClient({projectAccessKey: "public-api-key", projectId: "project-id"}); +// @ts-expect-error old authorizationScope initializer name is not supported. +new OMSClient({publicApiKey: "public-api-key", authorizationScope: "project-id"}); const session: OMSClientSessionState = defaultClient.wallet.session; const loginType: OMSClientSessionLoginType | undefined = defaultClient.wallet.session.loginType; +const polygonNetwork: Network = Networks.polygon; +const amoyNetwork: Network | undefined = findNetworkById(80002); +const baseNetwork: Network | undefined = findNetworkByName("base"); +const allNetworks: readonly Network[] = supportedNetworks; +const tokenContractInfo: TokenContractInfo = {symbol: "USDC", decimals: 6}; +const tokenMetadata: TokenMetadata = {tokenId: "0", name: "USDC"}; +const tokenBalance: TokenBalance = { + chainId: Networks.polygon.id, + contractInfo: tokenContractInfo, + tokenMetadata, + balanceUSD: "0.141799", + priceUSD: "1", +}; +const tokenBalancesPage: TokenBalancesPage = {page: 0, pageSize: 40, more: false}; +const tokenBalancesResult: TokenBalancesResult = {status: 200, page: tokenBalancesPage, balances: [tokenBalance]}; void session; void loginType; +void polygonNetwork; +void amoyNetwork; +void baseNetwork; +void allNetworks; +void tokenContractInfo; +void tokenMetadata; +void tokenBalancesResult; +void defaultClient.supportedNetworks; +// @ts-expect-error findNetworkById accepts numeric chain IDs only. +findNetworkById("80002"); +void defaultClient.indexer.getTokenBalances({ + network: Networks.polygon, + contractAddress: "0x2222222222222222222222222222222222222222", + walletAddress: "0x9999999999999999999999999999999999999999", + includeMetadata: false, +}); +void defaultClient.indexer.getTokenBalances({ + network: Networks.polygon, + walletAddress: "0x9999999999999999999999999999999999999999", + includeMetadata: true, + page: {page: 1, pageSize: 25}, +}); +void defaultClient.indexer.getTokenBalances({ + // @ts-expect-error Indexer public methods accept Network objects, not numeric chain IDs. + network: 137, + contractAddress: "0x2222222222222222222222222222222222222222", + walletAddress: "0x9999999999999999999999999999999999999999", + includeMetadata: false, +}); +void defaultClient.indexer.getTokenBalances({ + // @ts-expect-error chainId is not a public indexer parameter. + chainId: 137, + contractAddress: "0x2222222222222222222222222222222222222222", + walletAddress: "0x9999999999999999999999999999999999999999", + includeMetadata: false, +}); +void defaultClient.indexer.getNativeTokenBalance({ + network: Networks.polygon, + walletAddress: "0x9999999999999999999999999999999999999999", +}); +void defaultClient.indexer.getNativeTokenBalance({ + // @ts-expect-error Indexer public methods accept Network objects, not numeric chain IDs. + network: 137, + walletAddress: "0x9999999999999999999999999999999999999999", +}); +void defaultClient.indexer.getNativeTokenBalance({ + // @ts-expect-error chainId is not a public indexer parameter. + chainId: 137, + walletAddress: "0x9999999999999999999999999999999999999999", +}); void defaultClient.wallet.startOidcRedirectAuth({ provider: "google", redirectUri: "https://app.example/auth/callback", @@ -67,7 +157,8 @@ const customEnvironmentWithoutProviders = defineOmsEnvironment({ indexerUrlTemplate: "https://indexer.example/{value}", }); const customClient = new OMSClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", environment: customEnvironmentWithoutProviders, }); let broadlyTypedClient: OMSClient; @@ -80,14 +171,19 @@ void customClient.wallet.startOidcRedirectAuth({ }); function createClient(params: { - projectAccessKey: string; + publicApiKey: string; + projectId: string; environment?: OmsEnvironment; }) { return new OMSClient(params); } -void createClient({projectAccessKey: "project-key"}); void createClient({ - projectAccessKey: "project-key", + publicApiKey: "public-api-key", + projectId: "project-id", +}); +void createClient({ + publicApiKey: "public-api-key", + projectId: "project-id", environment: customEnvironmentWithoutProviders, });