From 18f33e228b776700d3156466a72166905bb7bbd0 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Mon, 15 Jun 2026 19:39:24 +0300 Subject: [PATCH 1/9] Update WaaS sandbox client --- API.md | 4 +- README.md | 2 + src/clients/walletClient.ts | 24 +++--- src/generated/waas.gen.ts | 141 +++++++++++++++++++++++++------ src/omsEnvironment.ts | 2 +- src/signedFetch.ts | 2 +- src/utils/requestUtils.ts | 2 +- tests/credentialSigner.test.ts | 6 +- tests/oidcRedirectAuth.test.ts | 2 +- tests/walletSigning.test.ts | 6 +- tests/walletTransactions.test.ts | 42 +++++++++ 11 files changed, 184 insertions(+), 49 deletions(-) diff --git a/API.md b/API.md index 382be2d..2cd857d 100644 --- a/API.md +++ b/API.md @@ -96,7 +96,7 @@ new OMSClient(params: { | Name | Type | Required | Description | |---|---|---|---| -| `publishableKey` | `string` | Yes | Your OMS publishable key. | +| `publishableKey` | `string` | Yes | Your OMS publishable key. Wallet requests send this as the WaaS `Api-Key` header. | | `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`. | @@ -887,7 +887,7 @@ interface OmsEnvironment { | `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. +The default is exported as `defaultOmsEnvironment`, uses `https://sandbox-api.dev.polygon-dev.technology` as the WaaS API base URL, and includes the `google` OIDC provider. Use `defineOmsEnvironment` to preserve typed custom OIDC provider keys: diff --git a/README.md b/README.md index 8807bdb..7ea10e9 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ const oms = new OMSClient({ }) ``` +By default, wallet requests use the sandbox WaaS API at `https://sandbox-api.dev.polygon-dev.technology`. + In Vite browser apps, keep those values in local environment variables: ```typescript diff --git a/src/clients/walletClient.ts b/src/clients/walletClient.ts index 18ec12a..da00f5e 100644 --- a/src/clients/walletClient.ts +++ b/src/clients/walletClient.ts @@ -27,8 +27,8 @@ import { import {OmsSessionError, OmsTransactionError, OmsWalletSelectionError, toOmsSdkError} from "../errors.js"; import { - Wallet as Walletclient, - WalletPublic as WalletPublicclient, + Waas as WaasClient, + WaasPublic as WaasPublicClient, WalletType, TransactionMode, TransactionStatus, @@ -331,8 +331,8 @@ class PendingWalletSelectionImpl implements PendingWalletSelection { } export class WalletClient { - private readonly client: Walletclient - private readonly publicClient: WalletPublicclient + private readonly client: WaasClient + private readonly publicClient: WaasPublicClient private readonly storage: StorageManager private readonly redirectAuthStorage?: StorageManager private readonly credentialSigner: CredentialSigner @@ -407,10 +407,10 @@ export class WalletClient { } const signedFetch = createSignedFetch(params.publishableKey, this.credentialSigner, this.projectId) - this.client = new Walletclient(params.environment.walletApiUrl, signedFetch) - this.publicClient = new WalletPublicclient( + this.client = new WaasClient(params.environment.walletApiUrl, signedFetch) + this.publicClient = new WaasPublicClient( params.environment.walletApiUrl, - createAccessKeyFetch(params.publishableKey), + createApiKeyFetch(params.publishableKey), ) this.indexerClient = new IndexerClient({ publishableKey: params.publishableKey, @@ -1620,7 +1620,11 @@ export class WalletClient { do { lastStatus = await this.client.transactionStatus({txnId: txnId} as TransactionStatusRequest) completedPolls += 1 - if (lastStatus.status === TransactionStatus.Executed || lastStatus.txnHash) { + if ( + lastStatus.status === TransactionStatus.Executed || + lastStatus.status === TransactionStatus.Failed || + lastStatus.txnHash + ) { return lastStatus } const pollDelayMs = this.transactionStatusPollDelayMs(completedPolls, options) @@ -1854,12 +1858,12 @@ function defaultRedirectAuthStorage(): StorageManager | undefined { : undefined } -function createAccessKeyFetch(publishableKey: string): Fetch { +function createApiKeyFetch(publishableKey: string): Fetch { return async (input: RequestInfo, init?: RequestInit): Promise => { const existingHeaders = (init?.headers ?? {}) as Record const headers: Record = { ...existingHeaders, - 'X-Access-Key': publishableKey, + 'Api-Key': publishableKey, } return globalThis.fetch(input, {...init, headers}) diff --git a/src/generated/waas.gen.ts b/src/generated/waas.gen.ts index 7bd46ad..6d30c21 100644 --- a/src/generated/waas.gen.ts +++ b/src/generated/waas.gen.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// waas v0.1.0 c7e8530922bd8ff83b9338cd080808244d972a3a +// waas v1-26.5.25-cc1eed0 f038c851ab6eacfa03422c6c4ed3c069c28e78ea // -- // Code generated by Webrpc-gen@v0.37.2 with typescript generator. DO NOT EDIT. // @@ -9,16 +9,16 @@ export const WebrpcVersion = "v1" // Schema version of your RIDL schema -export const WebrpcSchemaVersion = "v0.1.0" +export const WebrpcSchemaVersion = "v1-26.5.25-cc1eed0" // Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = "c7e8530922bd8ff83b9338cd080808244d972a3a" +export const WebrpcSchemaHash = "f038c851ab6eacfa03422c6c4ed3c069c28e78ea" // // Client interface // -export interface WalletClient { +export interface WaasClient { commitVerifier(req: CommitVerifierRequest, headers?: object, signal?: AbortSignal): Promise completeAuth(req: CompleteAuthRequest, headers?: object, signal?: AbortSignal): Promise @@ -47,7 +47,11 @@ export interface WalletClient { getIDToken(req: GetIDTokenRequest, headers?: object, signal?: AbortSignal): Promise } -export interface WalletPublicClient { +export interface WaasPublicClient { + status(headers?: object, signal?: AbortSignal): Promise + + getJWKS(headers?: object, signal?: AbortSignal): Promise + isValidMessageSignature(req: IsValidMessageSignatureRequest, headers?: object, signal?: AbortSignal): Promise isValidTypedDataSignature(req: IsValidTypedDataSignatureRequest, headers?: object, signal?: AbortSignal): Promise @@ -99,7 +103,8 @@ export enum SigningAlgorithm { export enum TransactionStatus { Quoted = 'quoted', Pending = 'pending', - Executed = 'executed' + Executed = 'executed', + Failed = 'failed' } export interface Identity { @@ -337,6 +342,17 @@ export interface GetIDTokenResponse { idToken: string } +export interface JWK { + alg: string + crv: string + kid: string + kty: string + use: string + x: string + y: string +} + + @@ -351,6 +367,21 @@ export interface GetIDTokenResponse { +export interface StatusRequest { +} + +export interface StatusResponse { + ver: string + env: string + pcr0: string +} + +export interface GetJWKSRequest { +} + +export interface GetJWKSResponse { + keys: Array +} @@ -360,10 +391,10 @@ export interface GetIDTokenResponse { // Client // -export class Wallet implements WalletClient { +export class Waas implements WaasClient { protected hostname: string protected fetch: Fetch - protected path = '/rpc/Wallet/' + protected path = '/v1/Waas/' constructor(hostname: string, fetch: Fetch) { this.hostname = hostname.replace(/\/*$/, '') @@ -375,20 +406,20 @@ export class Wallet implements WalletClient { } queryKey = { - commitVerifier: (req: CommitVerifierRequest) => ['Wallet', 'commitVerifier', req] as const, - completeAuth: (req: CompleteAuthRequest) => ['Wallet', 'completeAuth', req] as const, - createWallet: (req: CreateWalletRequest) => ['Wallet', 'createWallet', req] as const, - useWallet: (req: UseWalletRequest) => ['Wallet', 'useWallet', req] as const, - signMessage: (req: SignMessageRequest) => ['Wallet', 'signMessage', req] as const, - signTypedData: (req: SignTypedDataRequest) => ['Wallet', 'signTypedData', req] as const, - prepareEthereumTransaction: (req: PrepareEthereumTransactionRequest) => ['Wallet', 'prepareEthereumTransaction', req] as const, - prepareEthereumContractCall: (req: PrepareEthereumContractCallRequest) => ['Wallet', 'prepareEthereumContractCall', req] as const, - execute: (req: ExecuteRequest) => ['Wallet', 'execute', req] as const, - transactionStatus: (req: TransactionStatusRequest) => ['Wallet', 'transactionStatus', req] as const, - listAccess: (req: ListAccessRequest) => ['Wallet', 'listAccess', req] as const, - revokeAccess: (req: RevokeAccessRequest) => ['Wallet', 'revokeAccess', req] as const, - listWallets: (req: ListWalletsRequest) => ['Wallet', 'listWallets', req] as const, - getIDToken: (req: GetIDTokenRequest) => ['Wallet', 'getIDToken', req] as const, + commitVerifier: (req: CommitVerifierRequest) => ['Waas', 'commitVerifier', req] as const, + completeAuth: (req: CompleteAuthRequest) => ['Waas', 'completeAuth', req] as const, + createWallet: (req: CreateWalletRequest) => ['Waas', 'createWallet', req] as const, + useWallet: (req: UseWalletRequest) => ['Waas', 'useWallet', req] as const, + signMessage: (req: SignMessageRequest) => ['Waas', 'signMessage', req] as const, + signTypedData: (req: SignTypedDataRequest) => ['Waas', 'signTypedData', req] as const, + prepareEthereumTransaction: (req: PrepareEthereumTransactionRequest) => ['Waas', 'prepareEthereumTransaction', req] as const, + prepareEthereumContractCall: (req: PrepareEthereumContractCallRequest) => ['Waas', 'prepareEthereumContractCall', req] as const, + execute: (req: ExecuteRequest) => ['Waas', 'execute', req] as const, + transactionStatus: (req: TransactionStatusRequest) => ['Waas', 'transactionStatus', req] as const, + listAccess: (req: ListAccessRequest) => ['Waas', 'listAccess', req] as const, + revokeAccess: (req: RevokeAccessRequest) => ['Waas', 'revokeAccess', req] as const, + listWallets: (req: ListWalletsRequest) => ['Waas', 'listWallets', req] as const, + getIDToken: (req: GetIDTokenRequest) => ['Waas', 'getIDToken', req] as const, } commitVerifier = (req: CommitVerifierRequest, headers?: object, signal?: AbortSignal): Promise => { @@ -560,10 +591,10 @@ export class Wallet implements WalletClient { } } -export class WalletPublic implements WalletPublicClient { +export class WaasPublic implements WaasPublicClient { protected hostname: string protected fetch: Fetch - protected path = '/rpc/WalletPublic/' + protected path = '/v1/WaasPublic/' constructor(hostname: string, fetch: Fetch) { this.hostname = hostname.replace(/\/*$/, '') @@ -575,8 +606,34 @@ export class WalletPublic implements WalletPublicClient { } queryKey = { - isValidMessageSignature: (req: IsValidMessageSignatureRequest) => ['WalletPublic', 'isValidMessageSignature', req] as const, - isValidTypedDataSignature: (req: IsValidTypedDataSignatureRequest) => ['WalletPublic', 'isValidTypedDataSignature', req] as const, + status: () => ['WaasPublic', 'status'] as const, + getJWKS: () => ['WaasPublic', 'getJWKS'] as const, + isValidMessageSignature: (req: IsValidMessageSignatureRequest) => ['WaasPublic', 'isValidMessageSignature', req] as const, + isValidTypedDataSignature: (req: IsValidTypedDataSignatureRequest) => ['WaasPublic', 'isValidTypedDataSignature', req] as const, + } + + status = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('Status'), + createHttpRequest('{}', headers, signal)).then((res) => { + return buildResponse(res).then(_data => { + return JsonDecode(_data, 'StatusResponse') + }) + }, (error) => { + throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}` }) + }) + } + + getJWKS = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('GetJWKS'), + createHttpRequest('{}', headers, signal)).then((res) => { + return buildResponse(res).then(_data => { + return JsonDecode(_data, 'GetJWKSResponse') + }) + }, (error) => { + throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}` }) + }) } isValidMessageSignature = (req: IsValidMessageSignatureRequest, headers?: object, signal?: AbortSignal): Promise => { @@ -1066,6 +1123,18 @@ export class AuthMethodUnavailableError extends WebrpcError { } } +export class OTPRateLimitedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'OTPRateLimited' + this.code = typeof error.code === 'number' ? error.code : 7010 + this.message = error.message || `Too many OTP requests, try again later` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, OTPRateLimitedError.prototype) + } +} + export class WalletNotFoundError extends WebrpcError { constructor(error: WebrpcErrorParams = {}) { super(error) @@ -1198,6 +1267,18 @@ export class InvalidFeeOptionError extends WebrpcError { } } +export class TransactionBroadcastFailedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'TransactionBroadcastFailed' + this.code = typeof error.code === 'number' ? error.code : 7311 + this.message = error.message || `Transaction broadcast attempt failed; poll TransactionStatus to determine on-chain outcome` + this.status = typeof error.status === 'number' ? error.status : 502 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, TransactionBroadcastFailedError.prototype) + } +} + export enum errors { WebrpcEndpoint = 'WebrpcEndpoint', @@ -1232,6 +1313,7 @@ export enum errors { OAuthError = 'OAuthError', AccessError = 'AccessError', AuthMethodUnavailable = 'AuthMethodUnavailable', + OTPRateLimited = 'OTPRateLimited', WalletNotFound = 'WalletNotFound', AccountNotFound = 'AccountNotFound', AccountAlreadyExists = 'AccountAlreadyExists', @@ -1243,6 +1325,7 @@ export enum errors { TransactionNotFound = 'TransactionNotFound', TransactionExpired = 'TransactionExpired', InvalidFeeOption = 'InvalidFeeOption', + TransactionBroadcastFailed = 'TransactionBroadcastFailed', } export enum WebrpcErrorCodes { @@ -1278,6 +1361,7 @@ export enum WebrpcErrorCodes { OAuthError = 7006, AccessError = 7007, AuthMethodUnavailable = 7009, + OTPRateLimited = 7010, WalletNotFound = 7300, AccountNotFound = 7301, AccountAlreadyExists = 7302, @@ -1289,6 +1373,7 @@ export enum WebrpcErrorCodes { TransactionNotFound = 7308, TransactionExpired = 7309, InvalidFeeOption = 7310, + TransactionBroadcastFailed = 7311, } export const webrpcErrorByCode: { [code: number]: any } = { @@ -1324,6 +1409,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { [7006]: OAuthErrorError, [7007]: AccessErrorError, [7009]: AuthMethodUnavailableError, + [7010]: OTPRateLimitedError, [7300]: WalletNotFoundError, [7301]: AccountNotFoundError, [7302]: AccountAlreadyExistsError, @@ -1335,6 +1421,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { [7308]: TransactionNotFoundError, [7309]: TransactionExpiredError, [7310]: InvalidFeeOptionError, + [7311]: TransactionBroadcastFailedError, } @@ -1345,7 +1432,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { export const WebrpcHeader = "Webrpc" -export const WebrpcHeaderValue = "webrpc@v0.37.2;gen-typescript@v0.26.0;waas@v0.1.0" +export const WebrpcHeaderValue = "webrpc@v0.37.2;gen-typescript@v0.26.0;waas@v1-26.5.25-cc1eed0" type WebrpcGenVersions = { WebrpcGenVersion: string; diff --git a/src/omsEnvironment.ts b/src/omsEnvironment.ts index 85eaff2..b1b641c 100644 --- a/src/omsEnvironment.ts +++ b/src/omsEnvironment.ts @@ -24,7 +24,7 @@ export interface OmsEnvironment< } export const defaultOmsEnvironment = { - walletApiUrl: "https://d26giflyqapd29.cloudfront.net", + walletApiUrl: "https://sandbox-api.dev.polygon-dev.technology", indexerUrlTemplate: "https://dev-{value}-indexer.sequence.app/rpc/Indexer/", auth: { oidcProviders: { diff --git a/src/signedFetch.ts b/src/signedFetch.ts index 3ad4df0..a956047 100644 --- a/src/signedFetch.ts +++ b/src/signedFetch.ts @@ -32,7 +32,7 @@ export function createSignedFetch( const existingHeaders = (init?.headers ?? {}) as Record const headers: Record = { ...existingHeaders, - 'X-Access-Key': publishableKey, + 'Api-Key': publishableKey, 'OMS-Wallet-Signature': signatureHeader, } diff --git a/src/utils/requestUtils.ts b/src/utils/requestUtils.ts index 117c095..f788904 100644 --- a/src/utils/requestUtils.ts +++ b/src/utils/requestUtils.ts @@ -7,7 +7,7 @@ export class RequestUtils { scope: string, payload: string, ): string { - return `POST /rpc/Wallet${endpoint}\nnonce: ${nonce}\nscope: ${scope}\n\n${payload}`; + return `POST /v1/Waas${endpoint}\nnonce: ${nonce}\nscope: ${scope}\n\n${payload}`; } static buildWalletSignatureHeader( diff --git a/tests/credentialSigner.test.ts b/tests/credentialSigner.test.ts index de097f7..cb40c08 100644 --- a/tests/credentialSigner.test.ts +++ b/tests/credentialSigner.test.ts @@ -34,13 +34,13 @@ describe("wallet request signing", () => { vi.stubGlobal("fetch", fetchMock); const signedFetch = createSignedFetch("publishable-key", signer, "project-id"); - await signedFetch("https://wallet.example/rpc/Wallet/CommitVerifier", { + await signedFetch("https://wallet.example/v1/Waas/CommitVerifier", { method: "POST", body, }); expect(signer.preimages).toEqual([ - "POST /rpc/Wallet/CommitVerifier\n" + + "POST /v1/Waas/CommitVerifier\n" + "nonce: 42\n" + "scope: project-id\n\n" + body, @@ -48,7 +48,7 @@ describe("wallet request signing", () => { const headers = fetchMock.mock.calls[0][1]?.headers as Record; expect(headers).toMatchObject({ - "X-Access-Key": "publishable-key", + "Api-Key": "publishable-key", "OMS-Wallet-Signature": `alg="ecdsa-p256-sha256", scope="project-id", cred="0x04${"11".repeat(64)}", nonce=42, sig="0x${"22".repeat(64)}"`, }); }); diff --git a/tests/oidcRedirectAuth.test.ts b/tests/oidcRedirectAuth.test.ts index 3a13f69..66ad33b 100644 --- a/tests/oidcRedirectAuth.test.ts +++ b/tests/oidcRedirectAuth.test.ts @@ -51,7 +51,7 @@ describe("WalletClient OIDC redirect auth", () => { const url = input.toString(); const body = JSON.parse(init?.body as string); - expect(url).toBe("https://wallet.example/rpc/Wallet/CommitVerifier"); + expect(url).toBe("https://wallet.example/v1/Waas/CommitVerifier"); expect(body).toMatchObject({ identityType: "oidc", authMode: "auth-code-pkce", diff --git a/tests/walletSigning.test.ts b/tests/walletSigning.test.ts index 431c44b..c6bc1a5 100644 --- a/tests/walletSigning.test.ts +++ b/tests/walletSigning.test.ts @@ -37,7 +37,7 @@ describe("WalletClient signing", () => { const body = JSON.parse(init?.body as string); const headers = init?.headers as Record; - expect(headers["X-Access-Key"]).toBe("publishable-key"); + expect(headers["Api-Key"]).toBe("publishable-key"); expect(headers["OMS-Wallet-Signature"]).toContain('alg="ecdsa-p256-sha256"'); expect(headers.Authorization).toBeUndefined(); @@ -95,7 +95,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("publishable-key"); + expect((init?.headers as Record)["Api-Key"]).toBe("publishable-key"); const headers = init?.headers as Record; expect(headers["OMS-Wallet-Signature"]).toContain('alg="ecdsa-p256-sha256"'); expect(headers.Authorization).toBeUndefined(); @@ -136,7 +136,7 @@ describe("WalletClient signing", () => { const body = JSON.parse(init?.body as string); const headers = init?.headers as Record; - expect(headers["X-Access-Key"]).toBe("publishable-key"); + expect(headers["Api-Key"]).toBe("publishable-key"); expect(headers["OMS-Wallet-Signature"]).toBeUndefined(); expect(headers.Authorization).toBeUndefined(); diff --git a/tests/walletTransactions.test.ts b/tests/walletTransactions.test.ts index 0926e06..7a83aae 100644 --- a/tests/walletTransactions.test.ts +++ b/tests/walletTransactions.test.ts @@ -524,6 +524,48 @@ describe("WalletClient transactions", () => { }); }); + it("returns failed transaction status as terminal", async () => { + const fetchMock = vi.fn(async (input: RequestInfo | URL) => { + const url = input.toString(); + + if (url.endsWith("/PrepareEthereumTransaction")) { + return jsonResponse({ + txnId: "txn-failed", + status: "quoted", + feeOptions: [], + sponsored: true, + expiresAt: "2099-01-01T00:00:00Z", + }); + } + + if (url.endsWith("/Execute")) { + return jsonResponse({status: "pending"}); + } + + if (url.endsWith("/TransactionStatus")) { + return jsonResponse({status: "failed"}); + } + + throw new Error(`Unexpected request: ${url}`); + }); + vi.stubGlobal("fetch", fetchMock); + + const wallet = createWalletWithSession( + new MemoryStorageManager(), + "0x9999999999999999999999999999999999999999", + ); + + await expect(wallet.sendTransaction({ + network: Networks.polygon, + to: "0x1111111111111111111111111111111111111111", + value: 0n, + })).resolves.toEqual({ + txnId: "txn-failed", + status: TransactionStatus.Failed, + }); + expect(fetchMock.mock.calls.filter(([input]) => input.toString().endsWith("/TransactionStatus"))).toHaveLength(1); + }); + it("exposes transaction status lookup by transaction id", async () => { const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const url = input.toString(); From b46d7e5a508fd8c3344685769bcf66bdf3bebc10 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Mon, 15 Jun 2026 19:52:52 +0300 Subject: [PATCH 2/9] Require separate indexer API key --- API.md | 3 ++ README.md | 13 +++++-- .../node-contract-deploy-example/.env.example | 1 + .../node-contract-deploy-example/README.md | 2 +- .../deployErc20.ts | 2 ++ examples/node/README.md | 2 +- examples/node/signInFlow.ts | 2 ++ examples/react/.env.example | 1 + examples/react/README.md | 4 +-- examples/react/src/config.ts | 1 + examples/react/src/omsClient.ts | 3 +- examples/react/src/vite-env.d.ts | 1 + examples/trails-actions/.env.example | 5 +-- examples/trails-actions/README.md | 2 +- examples/trails-actions/src/config.ts | 1 + examples/trails-actions/src/omsClient.ts | 3 +- examples/trails-actions/src/vite-env.d.ts | 1 + examples/wagmi/.env.example | 1 + examples/wagmi/README.md | 2 +- examples/wagmi/src/config.ts | 1 + examples/wagmi/src/omsClient.ts | 3 +- examples/wagmi/src/vite-env.d.ts | 1 + packages/oms-wallet-wagmi-connector/README.md | 1 + src/clients/indexerClient.ts | 8 ++--- src/clients/walletClient.ts | 3 +- src/omsClient.ts | 4 ++- tests/errorContracts.test.ts | 1 + tests/indexerClient.test.ts | 6 ++-- tests/networks.test.ts | 1 + tests/oidcRedirectAuth.test.ts | 2 ++ tests/walletAccess.test.ts | 1 + tests/walletErrors.test.ts | 3 ++ tests/walletSession.test.ts | 34 +++++++++++++++++++ tests/walletSigning.test.ts | 1 + tests/walletTransactions.test.ts | 2 ++ type-tests/oidcProviderTypes.ts | 26 ++++++++++---- 36 files changed, 118 insertions(+), 30 deletions(-) diff --git a/API.md b/API.md index 2cd857d..1802266 100644 --- a/API.md +++ b/API.md @@ -75,6 +75,7 @@ import { OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', + indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) ``` @@ -84,6 +85,7 @@ const oms = new OMSClient({ ```typescript new OMSClient(params: { publishableKey: string + indexerApiKey: string projectId: string environment?: OmsEnvironment storage?: StorageManager @@ -97,6 +99,7 @@ new OMSClient(params: { | Name | Type | Required | Description | |---|---|---|---| | `publishableKey` | `string` | Yes | Your OMS publishable key. Wallet requests send this as the WaaS `Api-Key` header. | +| `indexerApiKey` | `string` | Yes | Your Indexer API key. Indexer requests send this as the `X-Access-Key` header. | | `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`. | diff --git a/README.md b/README.md index 7ea10e9..2925159 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ import { OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', + indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) ``` @@ -42,6 +43,7 @@ function requiredEnv(name: string, value: string | undefined): string { const oms = new OMSClient({ publishableKey: requiredEnv('VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY), + indexerApiKey: requiredEnv('VITE_OMS_INDEXER_API_KEY', import.meta.env.VITE_OMS_INDEXER_API_KEY), projectId: requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID), }) ``` @@ -69,7 +71,7 @@ To run it locally from the repository root: ```bash cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/react/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID in examples/react/.env.local pnpm dev:example ``` @@ -95,7 +97,7 @@ To run it locally from the repository root: ```bash cp examples/wagmi/.env.example examples/wagmi/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/wagmi/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID in examples/wagmi/.env.local pnpm dev:wagmi-example ``` @@ -109,7 +111,7 @@ To run it locally from the repository root: ```bash cp examples/trails-actions/.env.example examples/trails-actions/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/trails-actions/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID in examples/trails-actions/.env.local pnpm dev:trails-actions-example ``` @@ -121,6 +123,7 @@ import { parseUnits } from 'viem' const oms = new OMSClient({ publishableKey: 'your-publishable-key', + indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) @@ -188,6 +191,7 @@ Google redirect auth is configured on the default environment. The redirect auth ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', + indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) ``` @@ -243,6 +247,7 @@ The SDK makes expired sessions inactive before protected wallet operations and t ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', + indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) @@ -423,6 +428,7 @@ const tx = await oms.wallet.sendTransaction({ ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', + indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', environment: { walletApiUrl: 'https://staging-wallet.example.com', @@ -442,6 +448,7 @@ import { MemoryStorageManager, OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', + indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', storage: new MemoryStorageManager(), }) diff --git a/examples/node-contract-deploy-example/.env.example b/examples/node-contract-deploy-example/.env.example index 08c0ba0..2680918 100644 --- a/examples/node-contract-deploy-example/.env.example +++ b/examples/node-contract-deploy-example/.env.example @@ -1,3 +1,4 @@ OMS_PUBLISHABLE_KEY=your-publishable-key +OMS_INDEXER_API_KEY=your-indexer-api-key OMS_PROJECT_ID=your-oms-project-id # DEPLOY_SALT=0x0000000000000000000000000000000000000000000000000000000000000001 diff --git a/examples/node-contract-deploy-example/README.md b/examples/node-contract-deploy-example/README.md index fd5a793..b23154f 100644 --- a/examples/node-contract-deploy-example/README.md +++ b/examples/node-contract-deploy-example/README.md @@ -32,7 +32,7 @@ From the repository root: pnpm install pnpm build cp examples/node-contract-deploy-example/.env.example examples/node-contract-deploy-example/.env.local -# Fill OMS_PUBLISHABLE_KEY and OMS_PROJECT_ID in .env.local +# Fill OMS_PUBLISHABLE_KEY, OMS_INDEXER_API_KEY, and OMS_PROJECT_ID in .env.local pnpm dev:node-contract-deploy-example ``` diff --git a/examples/node-contract-deploy-example/deployErc20.ts b/examples/node-contract-deploy-example/deployErc20.ts index d71f84b..2386238 100644 --- a/examples/node-contract-deploy-example/deployErc20.ts +++ b/examples/node-contract-deploy-example/deployErc20.ts @@ -14,6 +14,7 @@ loadDotenv({path: join(exampleDir, ".env.local"), quiet: true}); loadDotenv({path: join(exampleDir, ".env"), quiet: true}); const publishableKey = requiredEnv("OMS_PUBLISHABLE_KEY", process.env.OMS_PUBLISHABLE_KEY); +const indexerApiKey = requiredEnv("OMS_INDEXER_API_KEY", process.env.OMS_INDEXER_API_KEY); const projectId = requiredEnv("OMS_PROJECT_ID", process.env.OMS_PROJECT_ID); const defaultDeployerAddress = "0xce0042B868300000d44A59004Da54A005ffdcf9f" as const satisfies Address; const deployerAddress = optionalAddress("DEPLOYER_ADDRESS", process.env.DEPLOYER_ADDRESS) ?? defaultDeployerAddress; @@ -45,6 +46,7 @@ async function main() { const client = new OMSClient({ publishableKey, + indexerApiKey, projectId, storage: new MemoryStorageManager(), }); diff --git a/examples/node/README.md b/examples/node/README.md index 0c6474a..f95c086 100644 --- a/examples/node/README.md +++ b/examples/node/README.md @@ -11,7 +11,7 @@ Run it from the repository root: ```bash pnpm install pnpm build -OMS_PUBLISHABLE_KEY=your-publishable-key OMS_PROJECT_ID=your-project-id pnpm dev:node-example +OMS_PUBLISHABLE_KEY=your-publishable-key OMS_INDEXER_API_KEY=your-indexer-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/signInFlow.ts b/examples/node/signInFlow.ts index 14bfd81..395a46b 100644 --- a/examples/node/signInFlow.ts +++ b/examples/node/signInFlow.ts @@ -2,6 +2,7 @@ import readline from "node:readline/promises"; import {MemoryStorageManager, Networks, OMSClient} from "@0xsequence/typescript-sdk"; const publishableKey = requiredEnv("OMS_PUBLISHABLE_KEY", process.env.OMS_PUBLISHABLE_KEY); +const indexerApiKey = requiredEnv("OMS_INDEXER_API_KEY", process.env.OMS_INDEXER_API_KEY); const projectId = requiredEnv("OMS_PROJECT_ID", process.env.OMS_PROJECT_ID); async function main() { @@ -18,6 +19,7 @@ async function main() { const client = new OMSClient({ publishableKey, + indexerApiKey, projectId, storage: new MemoryStorageManager(), }); diff --git a/examples/react/.env.example b/examples/react/.env.example index 6316db9..8289e05 100644 --- a/examples/react/.env.example +++ b/examples/react/.env.example @@ -1,2 +1,3 @@ VITE_OMS_PUBLISHABLE_KEY=your-publishable-key +VITE_OMS_INDEXER_API_KEY=your-indexer-api-key VITE_OMS_PROJECT_ID=your-oms-project-id diff --git a/examples/react/README.md b/examples/react/README.md index aea350f..6cd9f30 100644 --- a/examples/react/README.md +++ b/examples/react/README.md @@ -12,7 +12,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID pnpm dev:example ``` @@ -24,7 +24,7 @@ The example requires a publishable key and project ID. Configure them locally be ```bash cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID ``` The Amoy-only "ERC20 example" panel includes a WalletKit Dollar example using diff --git a/examples/react/src/config.ts b/examples/react/src/config.ts index 503b55b..973a717 100644 --- a/examples/react/src/config.ts +++ b/examples/react/src/config.ts @@ -2,6 +2,7 @@ export const PUBLISHABLE_KEY = requiredEnv( 'VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY, ) +export const INDEXER_API_KEY = requiredEnv('VITE_OMS_INDEXER_API_KEY', import.meta.env.VITE_OMS_INDEXER_API_KEY) export const PROJECT_ID = requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID) function requiredEnv(name: string, value: string | undefined): string { diff --git a/examples/react/src/omsClient.ts b/examples/react/src/omsClient.ts index 2b5c90d..ca2cec0 100644 --- a/examples/react/src/omsClient.ts +++ b/examples/react/src/omsClient.ts @@ -1,9 +1,10 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { INDEXER_API_KEY, PROJECT_ID, PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, + indexerApiKey: INDEXER_API_KEY, projectId: PROJECT_ID, }) diff --git a/examples/react/src/vite-env.d.ts b/examples/react/src/vite-env.d.ts index 37fee95..80d9569 100644 --- a/examples/react/src/vite-env.d.ts +++ b/examples/react/src/vite-env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string + readonly VITE_OMS_INDEXER_API_KEY?: string readonly VITE_OMS_PROJECT_ID?: string } diff --git a/examples/trails-actions/.env.example b/examples/trails-actions/.env.example index 83de793..8289e05 100644 --- a/examples/trails-actions/.env.example +++ b/examples/trails-actions/.env.example @@ -1,2 +1,3 @@ -VITE_OMS_PUBLISHABLE_KEY= -VITE_OMS_PROJECT_ID= +VITE_OMS_PUBLISHABLE_KEY=your-publishable-key +VITE_OMS_INDEXER_API_KEY=your-indexer-api-key +VITE_OMS_PROJECT_ID=your-oms-project-id diff --git a/examples/trails-actions/README.md b/examples/trails-actions/README.md index d096842..99812a5 100644 --- a/examples/trails-actions/README.md +++ b/examples/trails-actions/README.md @@ -12,7 +12,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/trails-actions/.env.example examples/trails-actions/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID pnpm dev:trails-actions-example ``` diff --git a/examples/trails-actions/src/config.ts b/examples/trails-actions/src/config.ts index 09ca60c..444d19f 100644 --- a/examples/trails-actions/src/config.ts +++ b/examples/trails-actions/src/config.ts @@ -1,4 +1,5 @@ export const PUBLISHABLE_KEY = requiredEnv('VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY) +export const INDEXER_API_KEY = requiredEnv('VITE_OMS_INDEXER_API_KEY', import.meta.env.VITE_OMS_INDEXER_API_KEY) export const PROJECT_ID = requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID) function requiredEnv(name: string, value: string | undefined): string { diff --git a/examples/trails-actions/src/omsClient.ts b/examples/trails-actions/src/omsClient.ts index 2b5c90d..ca2cec0 100644 --- a/examples/trails-actions/src/omsClient.ts +++ b/examples/trails-actions/src/omsClient.ts @@ -1,9 +1,10 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { INDEXER_API_KEY, PROJECT_ID, PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, + indexerApiKey: INDEXER_API_KEY, projectId: PROJECT_ID, }) diff --git a/examples/trails-actions/src/vite-env.d.ts b/examples/trails-actions/src/vite-env.d.ts index 37fee95..80d9569 100644 --- a/examples/trails-actions/src/vite-env.d.ts +++ b/examples/trails-actions/src/vite-env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string + readonly VITE_OMS_INDEXER_API_KEY?: string readonly VITE_OMS_PROJECT_ID?: string } diff --git a/examples/wagmi/.env.example b/examples/wagmi/.env.example index 9d0ccee..9347af6 100644 --- a/examples/wagmi/.env.example +++ b/examples/wagmi/.env.example @@ -1,3 +1,4 @@ VITE_OMS_PUBLISHABLE_KEY=your-publishable-key +VITE_OMS_INDEXER_API_KEY=your-indexer-api-key VITE_OMS_PROJECT_ID=your-oms-project-id VITE_TRAILS_API_KEY=AQAAAAAAAMDoWz-avqIIjXGH7JJlBSormpo diff --git a/examples/wagmi/README.md b/examples/wagmi/README.md index acccdb2..814bdf0 100644 --- a/examples/wagmi/README.md +++ b/examples/wagmi/README.md @@ -8,7 +8,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/wagmi/.env.example examples/wagmi/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID pnpm dev:wagmi-example ``` diff --git a/examples/wagmi/src/config.ts b/examples/wagmi/src/config.ts index 5f26fec..9e9907a 100644 --- a/examples/wagmi/src/config.ts +++ b/examples/wagmi/src/config.ts @@ -2,6 +2,7 @@ export const PUBLISHABLE_KEY = requiredEnv( 'VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY, ) +export const INDEXER_API_KEY = requiredEnv('VITE_OMS_INDEXER_API_KEY', import.meta.env.VITE_OMS_INDEXER_API_KEY) export const PROJECT_ID = requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID) export const TRAILS_API_KEY = import.meta.env.VITE_TRAILS_API_KEY diff --git a/examples/wagmi/src/omsClient.ts b/examples/wagmi/src/omsClient.ts index 2b5c90d..ca2cec0 100644 --- a/examples/wagmi/src/omsClient.ts +++ b/examples/wagmi/src/omsClient.ts @@ -1,9 +1,10 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { INDEXER_API_KEY, PROJECT_ID, PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, + indexerApiKey: INDEXER_API_KEY, projectId: PROJECT_ID, }) diff --git a/examples/wagmi/src/vite-env.d.ts b/examples/wagmi/src/vite-env.d.ts index fd73e38..2225909 100644 --- a/examples/wagmi/src/vite-env.d.ts +++ b/examples/wagmi/src/vite-env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string + readonly VITE_OMS_INDEXER_API_KEY?: string readonly VITE_OMS_PROJECT_ID?: string readonly VITE_TRAILS_API_KEY?: string } diff --git a/packages/oms-wallet-wagmi-connector/README.md b/packages/oms-wallet-wagmi-connector/README.md index 73e22c1..81382fb 100644 --- a/packages/oms-wallet-wagmi-connector/README.md +++ b/packages/oms-wallet-wagmi-connector/README.md @@ -12,6 +12,7 @@ import { omsWalletConnector } from '@0xsequence/oms-wallet-wagmi-connector' const oms = new OMSClient({ publishableKey: import.meta.env.VITE_OMS_PUBLISHABLE_KEY, + indexerApiKey: import.meta.env.VITE_OMS_INDEXER_API_KEY, projectId: import.meta.env.VITE_OMS_PROJECT_ID, }) diff --git a/src/clients/indexerClient.ts b/src/clients/indexerClient.ts index 3f7fb96..c9c7633 100644 --- a/src/clients/indexerClient.ts +++ b/src/clients/indexerClient.ts @@ -155,15 +155,15 @@ export interface OmsEnvironment { } export class IndexerClient { - private readonly publishableKey: string; + private readonly indexerApiKey: string; private readonly environment: OmsEnvironment; private readonly client: HttpClient; constructor(params: { - publishableKey: string, + indexerApiKey: string, environment: OmsEnvironment }) { - this.publishableKey = params.publishableKey; + this.indexerApiKey = params.indexerApiKey; this.environment = params.environment; this.client = new HttpClient(); } @@ -293,7 +293,7 @@ export class IndexerClient { private defaultHeaders(): Record { return { - "X-Access-Key": this.publishableKey, + "X-Access-Key": this.indexerApiKey, Accept: "application/json", }; } diff --git a/src/clients/walletClient.ts b/src/clients/walletClient.ts index da00f5e..5bbf6de 100644 --- a/src/clients/walletClient.ts +++ b/src/clients/walletClient.ts @@ -360,6 +360,7 @@ export class WalletClient { constructor(params: { publishableKey: string, + indexerApiKey: string, projectId: string, environment: Env, storage?: StorageManager @@ -413,7 +414,7 @@ export class WalletClient { createApiKeyFetch(params.publishableKey), ) this.indexerClient = new IndexerClient({ - publishableKey: params.publishableKey, + indexerApiKey: params.indexerApiKey, environment: params.environment, }) } diff --git a/src/omsClient.ts b/src/omsClient.ts index 6aa124c..844a624 100644 --- a/src/omsClient.ts +++ b/src/omsClient.ts @@ -7,6 +7,7 @@ import {supportedNetworks} from "./networks.js"; interface OMSClientBaseParams { publishableKey: string; + indexerApiKey: string; projectId: string; storage?: StorageManager; redirectAuthStorage?: StorageManager; @@ -29,6 +30,7 @@ class OMSClientImpl { this.wallet = new WalletClient({ publishableKey: params.publishableKey, + indexerApiKey: params.indexerApiKey, projectId: params.projectId, environment, storage, @@ -37,7 +39,7 @@ class OMSClientImpl { }); this.indexer = new IndexerClient({ - publishableKey: params.publishableKey, + indexerApiKey: params.indexerApiKey, environment }); } diff --git a/tests/errorContracts.test.ts b/tests/errorContracts.test.ts index 3f73afc..6227e8a 100644 --- a/tests/errorContracts.test.ts +++ b/tests/errorContracts.test.ts @@ -1573,6 +1573,7 @@ function createOmsClient(params: { } = {}): OMSClient { const clientParams: ConstructorParameters[0] = { publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", storage: new MemoryStorageManager(), credentialSigner: params.credentialSigner ?? new MockSigner(), diff --git a/tests/indexerClient.test.ts b/tests/indexerClient.test.ts index ebde7e2..7323437 100644 --- a/tests/indexerClient.test.ts +++ b/tests/indexerClient.test.ts @@ -31,7 +31,7 @@ describe("IndexerClient", () => { vi.stubGlobal("fetch", fetchMock); const indexer = new IndexerClient({ - publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", environment: testEnvironment(), }); @@ -68,7 +68,7 @@ describe("IndexerClient", () => { vi.stubGlobal("fetch", fetchMock); const indexer = new IndexerClient({ - publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", environment: testEnvironment(), }); @@ -89,7 +89,7 @@ describe("IndexerClient", () => { vi.stubGlobal("fetch", vi.fn(async () => new Response("Bad Gateway", {status: 502}))); const indexer = new IndexerClient({ - publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", environment: testEnvironment(), }); diff --git a/tests/networks.test.ts b/tests/networks.test.ts index a7ffbc5..7101f50 100644 --- a/tests/networks.test.ts +++ b/tests/networks.test.ts @@ -51,6 +51,7 @@ describe("Networks", () => { it("is available from OMSClient", () => { const oms = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", }); diff --git a/tests/oidcRedirectAuth.test.ts b/tests/oidcRedirectAuth.test.ts index 66ad33b..92dbb91 100644 --- a/tests/oidcRedirectAuth.test.ts +++ b/tests/oidcRedirectAuth.test.ts @@ -142,6 +142,7 @@ describe("WalletClient OIDC redirect auth", () => { const storage = new MemoryStorageManager(); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -810,6 +811,7 @@ function createWalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: params.projectId ?? "project-id", environment, storage: new MemoryStorageManager(), diff --git a/tests/walletAccess.test.ts b/tests/walletAccess.test.ts index b204715..a861576 100644 --- a/tests/walletAccess.test.ts +++ b/tests/walletAccess.test.ts @@ -94,6 +94,7 @@ describe("WalletClient access management", () => { function createWalletWithSession(): WalletClient { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), diff --git a/tests/walletErrors.test.ts b/tests/walletErrors.test.ts index 2b85563..ae04d66 100644 --- a/tests/walletErrors.test.ts +++ b/tests/walletErrors.test.ts @@ -33,6 +33,7 @@ describe("WalletClient errors", () => { it("wraps local validation failures separately from request failures", async () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -54,6 +55,7 @@ describe("WalletClient errors", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -86,6 +88,7 @@ describe("WalletClient errors", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), diff --git a/tests/walletSession.test.ts b/tests/walletSession.test.ts index a3ce14c..032cd29 100644 --- a/tests/walletSession.test.ts +++ b/tests/walletSession.test.ts @@ -52,6 +52,7 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), credentialSigner: new MockSigner(), @@ -70,6 +71,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -94,6 +96,7 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -153,6 +156,7 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -199,6 +203,7 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -235,6 +240,7 @@ describe("WalletClient session storage", () => { const nextOnSessionExpired = vi.fn(); const nextClient = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -269,6 +275,7 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -297,6 +304,7 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -328,6 +336,7 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -377,6 +386,7 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -406,6 +416,7 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -478,6 +489,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -522,6 +534,7 @@ describe("WalletClient session storage", () => { redirectAuthStorage.set(Constants.redirectAuthStorageKey, JSON.stringify({verifier: "old-verifier"})); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -582,6 +595,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -611,6 +625,7 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -667,6 +682,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -726,6 +742,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -760,6 +777,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -827,6 +845,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -888,6 +907,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -943,6 +963,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1002,6 +1023,7 @@ describe("WalletClient session storage", () => { const storage = new MemoryStorageManager(); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -1070,6 +1092,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1112,6 +1135,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1167,6 +1191,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1219,6 +1244,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1264,6 +1290,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1315,6 +1342,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1366,6 +1394,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1417,6 +1446,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1448,6 +1478,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1489,6 +1520,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -1541,6 +1573,7 @@ describe("WalletClient session storage", () => { const storage = new MemoryStorageManager(); const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -1586,6 +1619,7 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), diff --git a/tests/walletSigning.test.ts b/tests/walletSigning.test.ts index c6bc1a5..c524008 100644 --- a/tests/walletSigning.test.ts +++ b/tests/walletSigning.test.ts @@ -184,6 +184,7 @@ describe("WalletClient signing", () => { function createWalletWithSession(walletAddress: string): WalletClient { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), diff --git a/tests/walletTransactions.test.ts b/tests/walletTransactions.test.ts index 7a83aae..75863e9 100644 --- a/tests/walletTransactions.test.ts +++ b/tests/walletTransactions.test.ts @@ -585,6 +585,7 @@ describe("WalletClient transactions", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -644,6 +645,7 @@ describe("WalletClient transactions", () => { function createWalletWithSession(storage: MemoryStorageManager, walletAddress: string): WalletClient { const wallet = new WalletClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, diff --git a/type-tests/oidcProviderTypes.ts b/type-tests/oidcProviderTypes.ts index 6d2e6f2..1df13fa 100644 --- a/type-tests/oidcProviderTypes.ts +++ b/type-tests/oidcProviderTypes.ts @@ -71,20 +71,28 @@ if (false) { const defaultClient = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", }); // @ts-expect-error publishableKey is required. -new OMSClient({projectId: "project-id"}); +new OMSClient({indexerApiKey: "indexer-api-key", projectId: "project-id"}); +// @ts-expect-error indexerApiKey is required. +new OMSClient({publishableKey: "publishable-key", projectId: "project-id"}); // @ts-expect-error projectId is required. -new OMSClient({publishableKey: "publishable-key"}); +new OMSClient({publishableKey: "publishable-key", indexerApiKey: "indexer-api-key"}); // @ts-expect-error old projectAccessKey initializer name is not supported. -new OMSClient({projectAccessKey: "publishable-key", projectId: "project-id"}); +new OMSClient({projectAccessKey: "publishable-key", indexerApiKey: "indexer-api-key", projectId: "project-id"}); // @ts-expect-error old publicApiKey initializer name is not supported. -new OMSClient({publicApiKey: "publishable-key", projectId: "project-id"}); +new OMSClient({publicApiKey: "publishable-key", indexerApiKey: "indexer-api-key", projectId: "project-id"}); // @ts-expect-error old authorizationScope initializer name is not supported. -new OMSClient({publishableKey: "publishable-key", authorizationScope: "project-id"}); -// @ts-expect-error session expiry is subscribed through wallet.onSessionExpired, not constructor params. -new OMSClient({publishableKey: "publishable-key", projectId: "project-id", onSessionExpired: () => {}}); +new OMSClient({publishableKey: "publishable-key", indexerApiKey: "indexer-api-key", authorizationScope: "project-id"}); +new OMSClient({ + publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", + projectId: "project-id", + // @ts-expect-error session expiry is subscribed through wallet.onSessionExpired, not constructor params. + onSessionExpired: () => {}, +}); const session: OMSClientSessionState = defaultClient.wallet.session; const unsubscribeSessionExpired: () => void = defaultClient.wallet.onSessionExpired(({session}) => { void session.sessionEmail; @@ -194,6 +202,7 @@ const customEnvironmentWithoutProviders = defineOmsEnvironment({ }); const customClient = new OMSClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: customEnvironmentWithoutProviders, }); @@ -208,6 +217,7 @@ void customClient.wallet.startOidcRedirectAuth({ function createClient(params: { publishableKey: string; + indexerApiKey: string; projectId: string; environment?: OmsEnvironment; }) { @@ -216,10 +226,12 @@ function createClient(params: { void createClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", }); void createClient({ publishableKey: "publishable-key", + indexerApiKey: "indexer-api-key", projectId: "project-id", environment: customEnvironmentWithoutProviders, }); From a2a14c4cb5e01605c3c232a7f2d751ec2b4b91b5 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Thu, 18 Jun 2026 14:05:04 +0300 Subject: [PATCH 3/9] Use IndexerGateway for balances and history --- API.md | 117 +++-- README.md | 36 +- docs/error-contracts.md | 4 +- .../node-contract-deploy-example/.env.example | 1 - .../node-contract-deploy-example/README.md | 2 +- .../deployErc20.ts | 2 - examples/node/README.md | 2 +- examples/node/signInFlow.ts | 2 - examples/react/.env.example | 1 - examples/react/README.md | 4 +- examples/react/src/config.ts | 1 - examples/react/src/omsClient.ts | 3 +- examples/react/src/vite-env.d.ts | 1 - examples/trails-actions/.env.example | 1 - examples/trails-actions/README.md | 2 +- examples/trails-actions/src/config.ts | 1 - examples/trails-actions/src/omsClient.ts | 3 +- examples/trails-actions/src/trailsActions.ts | 24 +- examples/trails-actions/src/vite-env.d.ts | 1 - examples/wagmi/.env.example | 1 - examples/wagmi/README.md | 2 +- examples/wagmi/src/config.ts | 1 - examples/wagmi/src/omsClient.ts | 3 +- examples/wagmi/src/vite-env.d.ts | 1 - packages/oms-wallet-wagmi-connector/README.md | 1 - src/clients/indexerClient.ts | 407 ++++++++++++++---- src/clients/walletClient.ts | 69 ++- src/index.ts | 11 +- src/omsClient.ts | 4 +- src/omsEnvironment.ts | 4 +- src/operations.ts | 4 +- tests/errorContracts.test.ts | 65 ++- tests/indexerClient.test.ts | 236 ++++++++-- tests/networks.test.ts | 1 - tests/oidcRedirectAuth.test.ts | 10 +- tests/walletAccess.test.ts | 3 +- tests/walletErrors.test.ts | 5 +- tests/walletSession.test.ts | 36 +- tests/walletSigning.test.ts | 3 +- tests/walletTransactions.test.ts | 94 ++-- type-tests/oidcProviderTypes.ts | 80 ++-- 41 files changed, 817 insertions(+), 432 deletions(-) diff --git a/API.md b/API.md index 1802266..fbac41f 100644 --- a/API.md +++ b/API.md @@ -33,8 +33,8 @@ - [listAccessPages](#listaccesspages) - [revokeAccess](#revokeaccess) - [IndexerClient](#indexerclient) - - [getTokenBalances](#gettokenbalances) - - [getNativeTokenBalance](#getnativetokenbalance) + - [getBalances](#getbalances) + - [getTransactionHistory](#gettransactionhistory) - [Errors](#errors) - [Types](#types) - [Network](#network) @@ -55,7 +55,8 @@ - [SendTransactionResponse](#sendtransactionresponse) - [TransactionStatusPollingOptions](#transactionstatuspollingoptions) - [FeeOptionSelector](#feeoptionselector) - - [TokenBalancesResult](#tokenbalancesresult) + - [BalancesResult](#balancesresult) + - [TransactionHistoryResult](#transactionhistoryresult) - [TokenBalancesPage](#tokenbalancespage) - [TokenBalance](#tokenbalance) - [TokenContractInfo](#tokencontractinfo) @@ -75,7 +76,6 @@ import { OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', - indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) ``` @@ -85,7 +85,6 @@ const oms = new OMSClient({ ```typescript new OMSClient(params: { publishableKey: string - indexerApiKey: string projectId: string environment?: OmsEnvironment storage?: StorageManager @@ -98,8 +97,7 @@ new OMSClient(params: { | Name | Type | Required | Description | |---|---|---|---| -| `publishableKey` | `string` | Yes | Your OMS publishable key. Wallet requests send this as the WaaS `Api-Key` header. | -| `indexerApiKey` | `string` | Yes | Your Indexer API key. Indexer requests send this as the `X-Access-Key` header. | +| `publishableKey` | `string` | Yes | Your OMS publishable key. Wallet and IndexerGateway requests send this as the `Api-Key` header. | | `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`. | @@ -703,36 +701,39 @@ if (other) { ## IndexerClient -Accessed via `oms.indexer`. Queries on-chain token balances through the OMS Indexer API. +Accessed via `oms.indexer`. Queries on-chain balances and transaction history through IndexerGateway. -### getTokenBalances +### getBalances ```typescript -getTokenBalances(params: { - network: Network - contractAddress?: string +getBalances(params: { walletAddress: string - includeMetadata: boolean - page?: { - page?: number - pageSize?: number - } -}): Promise + networks?: Network[] + networkType?: 'MAINNETS' | 'TESTNETS' | 'ALL' + contractAddresses?: string[] + includeMetadata?: boolean + omitPrices?: boolean + tokenIds?: string[] + page?: TokenBalancesPage +}): Promise ``` -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`. +Fetches native and token balances for a wallet. Pass `networks` to query explicit SDK network objects. If `networks` is omitted, the request defaults to `networkType: 'MAINNETS'`. The default request returns page `0` with up to `40` entries. `includeMetadata` defaults to `true`; token display data is returned on `contractInfo` and `tokenMetadata`, and ERC-20 decimals are available as `contractInfo.decimals`. **Parameters** | Name | Type | Description | |---|---|---| -| `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 }`. | +| `networks` | `Network[]` | Optional explicit networks to query. Use exported registry values such as `Networks.polygon`. | +| `networkType` | `'MAINNETS' \| 'TESTNETS' \| 'ALL'` | Optional gateway network group when `networks` is omitted. Defaults to `'MAINNETS'`. | +| `contractAddresses` | `string[]` | Optional token contract filter. Omit to query balances across contracts. | +| `includeMetadata` | `boolean` | Optional metadata flag. Defaults to `true`. | +| `omitPrices` | `boolean` | Optional price exclusion flag. | +| `tokenIds` | `string[]` | Optional token ID filter. | +| `page` | `TokenBalancesPage` | Optional pagination request. Defaults to `{ page: 0, pageSize: 40 }`. | -**Returns** `Promise` — see [TokenBalancesResult](#tokenbalancesresult). +**Returns** `Promise` — see [BalancesResult](#balancesresult). **Example** @@ -740,12 +741,16 @@ Fetches token balances for a wallet on a given network. Omit `contractAddress` t const { walletAddress } = oms.wallet if (!walletAddress) throw new Error('No active wallet session') -const result = await oms.indexer.getTokenBalances({ - network: Networks.polygon, +const result = await oms.indexer.getBalances({ + networks: [Networks.polygon], walletAddress, includeMetadata: true, }) +for (const b of result.nativeBalances) { + console.log(b.symbol, b.balance) +} + for (const b of result.balances) { console.log(b.contractAddress, b.balance, b.tokenId) } @@ -753,16 +758,26 @@ for (const b of result.balances) { --- -### getNativeTokenBalance +### getTransactionHistory ```typescript -getNativeTokenBalance(params: { - network: Network +getTransactionHistory(params: { walletAddress: string -}): Promise + networks?: Network[] + networkType?: 'MAINNETS' | 'TESTNETS' | 'ALL' + contractAddresses?: string[] + transactionHashes?: string[] + metaTransactionIds?: string[] + fromBlock?: number + toBlock?: number + tokenId?: string + includeMetadata?: boolean + omitPrices?: boolean + page?: TokenBalancesPage +}): Promise ``` -Fetches the native token balance for a wallet. This is also used internally to enrich transaction fee options. +Fetches mined transaction history for a wallet. Pass `networks` to query explicit SDK network objects. If `networks` is omitted, the request defaults to `networkType: 'MAINNETS'`. `includeMetadata` defaults to `true`. --- @@ -877,7 +892,7 @@ findNetworkByName(name: string): Network | undefined ```typescript interface OmsEnvironment { walletApiUrl: string - indexerUrlTemplate: string + indexerGatewayUrl: string auth?: { oidcProviders?: Record } @@ -887,7 +902,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 selected network name, e.g. `"https://indexer.example.com/{value}"`. | +| `indexerGatewayUrl` | `string` | Base URL of the IndexerGateway host, e.g. `"https://api.example.com/v1/IndexerGateway/"`. | | `auth.oidcProviders` | `Record` | OIDC provider configurations addressable by provider key. | The default is exported as `defaultOmsEnvironment`, uses `https://sandbox-api.dev.polygon-dev.technology` as the WaaS API base URL, and includes the `google` OIDC provider. @@ -1185,12 +1200,13 @@ that fee option. --- -### TokenBalancesResult +### BalancesResult ```typescript -interface TokenBalancesResult { +interface BalancesResult { status: number page?: TokenBalancesPage + nativeBalances: TokenBalance[] balances: TokenBalance[] } ``` @@ -1199,25 +1215,48 @@ interface TokenBalancesResult { |---|---|---| | `status` | `number` | HTTP status code of the indexer response. | | `page` | `TokenBalancesPage` | Pagination metadata, if present. | +| `nativeBalances` | `TokenBalance[]` | Native token balances for the requested address. | | `balances` | `TokenBalance[]` | Array of token balance entries for the requested address. | --- +### TransactionHistoryResult + +```typescript +interface TransactionHistoryResult { + status: number + page?: TokenBalancesPage + transactions: Transaction[] +} +``` + +| Field | Type | Description | +|---|---|---| +| `status` | `number` | HTTP status code of the indexer response. | +| `page` | `TokenBalancesPage` | Pagination metadata, if present. | +| `transactions` | `Transaction[]` | Flattened transaction entries across the requested networks. | + +--- + ### TokenBalancesPage ```typescript interface TokenBalancesPage { - page: number - pageSize: number - more: boolean + page?: number + pageSize?: number + more?: boolean + before?: unknown + after?: unknown } ``` | Field | Type | Description | |---|---|---| | `page` | `number` | Current page index (zero-based). | -| `pageSize` | `number` | Number of entries per page (up to 40). | +| `pageSize` | `number` | Number of entries per page. | | `more` | `boolean` | `true` if additional pages are available. | +| `before` | `unknown` | Gateway cursor before the current page, when returned. | +| `after` | `unknown` | Gateway cursor after the current page, when returned. | --- diff --git a/README.md b/README.md index 2925159..696f96c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ import { OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', - indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) ``` @@ -43,7 +42,6 @@ function requiredEnv(name: string, value: string | undefined): string { const oms = new OMSClient({ publishableKey: requiredEnv('VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY), - indexerApiKey: requiredEnv('VITE_OMS_INDEXER_API_KEY', import.meta.env.VITE_OMS_INDEXER_API_KEY), projectId: requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID), }) ``` @@ -71,7 +69,7 @@ To run it locally from the repository root: ```bash cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID in examples/react/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/react/.env.local pnpm dev:example ``` @@ -97,7 +95,7 @@ To run it locally from the repository root: ```bash cp examples/wagmi/.env.example examples/wagmi/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID in examples/wagmi/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/wagmi/.env.local pnpm dev:wagmi-example ``` @@ -111,7 +109,7 @@ To run it locally from the repository root: ```bash cp examples/trails-actions/.env.example examples/trails-actions/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID in examples/trails-actions/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/trails-actions/.env.local pnpm dev:trails-actions-example ``` @@ -123,7 +121,6 @@ import { parseUnits } from 'viem' const oms = new OMSClient({ publishableKey: 'your-publishable-key', - indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) @@ -191,7 +188,6 @@ Google redirect auth is configured on the default environment. The redirect auth ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', - indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) ``` @@ -247,7 +243,6 @@ The SDK makes expired sessions inactive before protected wallet operations and t ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', - indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', }) @@ -428,16 +423,15 @@ const tx = await oms.wallet.sendTransaction({ ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', - indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', environment: { walletApiUrl: 'https://staging-wallet.example.com', - indexerUrlTemplate: 'https://staging-indexer.example.com/{value}', + indexerGatewayUrl: 'https://staging-indexer.example.com/v1/IndexerGateway/', }, }) ``` -For indexer requests, `{value}` is replaced with the selected `Network.name`, such as `polygon` or `amoy`. +For indexer requests, `indexerGatewayUrl` points at the IndexerGateway base URL. ### Custom Storage and Signing @@ -448,7 +442,6 @@ import { MemoryStorageManager, OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', - indexerApiKey: 'your-indexer-api-key', projectId: 'your-project-id', storage: new MemoryStorageManager(), }) @@ -506,31 +499,28 @@ const tx = await oms.wallet.callContract({ }) ``` -### Query Token and Native Balances +### Query Balances ```typescript const { walletAddress } = oms.wallet if (!walletAddress) throw new Error('No active wallet session') -const result = await oms.indexer.getTokenBalances({ - network: Networks.polygon, +const result = await oms.indexer.getBalances({ + networks: [Networks.polygon], walletAddress, includeMetadata: true, }) +for (const b of result.nativeBalances) { + console.log(b.symbol, b.balance) +} + for (const b of result.balances) { console.log(b.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. +Pass `contractAddresses` to filter balances to specific token contracts. Omit `networks` to query mainnets by default, or pass `networkType: 'TESTNETS'` / `'ALL'`. With `includeMetadata: true`, ERC-20 decimals are available as `contractInfo.decimals`. The response is paginated; pass `page` when requesting later pages. ### Manage Access diff --git a/docs/error-contracts.md b/docs/error-contracts.md index b47ac67..67e4579 100644 --- a/docs/error-contracts.md +++ b/docs/error-contracts.md @@ -33,8 +33,8 @@ supports, whether `upstreamError` should be present, and which test owns the con | `oms.wallet.sendTransaction`, `callContract` | Submitted transaction status polling fails | `OmsTransactionError`, `OMS_TRANSACTION_STATUS_LOOKUP_FAILED`, `retryable: true`, `txnId` | Retry status lookup, not the original write | Present when polling crossed transport/upstream boundary | `snapshots transaction status polling failures with txn and upstream details`; `snapshots transaction status polling backend errors as retryable` | | `oms.wallet.getTransactionStatus` | Direct status lookup backend failure | `OmsRequestError` or `OmsResponseError` with status operation | Retry status lookup or surface backend status to the user | Present | `snapshots direct transaction status backend errors with upstream details` | | `oms.wallet.listAccess`, `listAccessPages`, `revokeAccess` | WaaS access backend failure | `OmsRequestError` or `OmsResponseError` with access operation | Retry based on SDK code/status; log upstream detail | Present | `snapshots access backend errors with upstream details` | -| `oms.indexer.getTokenBalances`, `getNativeTokenBalance` | Indexer backend, transport, malformed JSON, or malformed payload | `OmsRequestError` or `OmsResponseError` with indexer operation | Retry based on SDK code/status; log upstream detail | Present for remote/transport response failures | `snapshots indexer backend errors with upstream details`; `snapshots native balance indexer errors with upstream details`; `snapshots indexer transport failures with upstream details`; `snapshots indexer malformed response errors with upstream details` | -| `oms.indexer.getTokenBalances`, `getNativeTokenBalance` | Indexer non-JSON HTTP body | `OmsRequestError`, `OMS_HTTP_ERROR`, sanitized message | Do not expose raw upstream HTML/text bodies; log normalized detail | Present, sanitized | `snapshots indexer non-JSON HTTP errors without raw upstream bodies` | +| `oms.indexer.getBalances`, `getTransactionHistory` | Indexer backend, transport, malformed JSON, or malformed payload | `OmsRequestError` or `OmsResponseError` with indexer operation | Retry based on SDK code/status; log upstream detail | Present for remote/transport response failures | `snapshots indexer backend errors with upstream details`; `snapshots transaction history indexer errors with upstream details`; `snapshots indexer transport failures with upstream details`; `snapshots indexer malformed response errors with upstream details` | +| `oms.indexer.getBalances`, `getTransactionHistory` | Indexer non-JSON HTTP body | `OmsRequestError`, `OMS_HTTP_ERROR`, sanitized message | Do not expose raw upstream HTML/text bodies; log normalized detail | Present, sanitized | `snapshots indexer non-JSON HTTP errors without raw upstream bodies` | | Exported storage managers and credential signers | Local runtime capability or storage failure | Native `Error` or signer/storage-specific runtime error | Fix local runtime support or storage availability | Absent | `snapshots exported storage and signer runtime errors` | | Exported `OmsSdkError` classes and `isOmsSdkError` | Error class/helper field contract | Stable public fields on constructed errors | Use only when the error class/helper is the unit under test | As constructed | `snapshots exported error helper and subclass fields` | diff --git a/examples/node-contract-deploy-example/.env.example b/examples/node-contract-deploy-example/.env.example index 2680918..08c0ba0 100644 --- a/examples/node-contract-deploy-example/.env.example +++ b/examples/node-contract-deploy-example/.env.example @@ -1,4 +1,3 @@ OMS_PUBLISHABLE_KEY=your-publishable-key -OMS_INDEXER_API_KEY=your-indexer-api-key OMS_PROJECT_ID=your-oms-project-id # DEPLOY_SALT=0x0000000000000000000000000000000000000000000000000000000000000001 diff --git a/examples/node-contract-deploy-example/README.md b/examples/node-contract-deploy-example/README.md index b23154f..fd5a793 100644 --- a/examples/node-contract-deploy-example/README.md +++ b/examples/node-contract-deploy-example/README.md @@ -32,7 +32,7 @@ From the repository root: pnpm install pnpm build cp examples/node-contract-deploy-example/.env.example examples/node-contract-deploy-example/.env.local -# Fill OMS_PUBLISHABLE_KEY, OMS_INDEXER_API_KEY, and OMS_PROJECT_ID in .env.local +# Fill OMS_PUBLISHABLE_KEY and OMS_PROJECT_ID in .env.local pnpm dev:node-contract-deploy-example ``` diff --git a/examples/node-contract-deploy-example/deployErc20.ts b/examples/node-contract-deploy-example/deployErc20.ts index 2386238..d71f84b 100644 --- a/examples/node-contract-deploy-example/deployErc20.ts +++ b/examples/node-contract-deploy-example/deployErc20.ts @@ -14,7 +14,6 @@ loadDotenv({path: join(exampleDir, ".env.local"), quiet: true}); loadDotenv({path: join(exampleDir, ".env"), quiet: true}); const publishableKey = requiredEnv("OMS_PUBLISHABLE_KEY", process.env.OMS_PUBLISHABLE_KEY); -const indexerApiKey = requiredEnv("OMS_INDEXER_API_KEY", process.env.OMS_INDEXER_API_KEY); const projectId = requiredEnv("OMS_PROJECT_ID", process.env.OMS_PROJECT_ID); const defaultDeployerAddress = "0xce0042B868300000d44A59004Da54A005ffdcf9f" as const satisfies Address; const deployerAddress = optionalAddress("DEPLOYER_ADDRESS", process.env.DEPLOYER_ADDRESS) ?? defaultDeployerAddress; @@ -46,7 +45,6 @@ async function main() { const client = new OMSClient({ publishableKey, - indexerApiKey, projectId, storage: new MemoryStorageManager(), }); diff --git a/examples/node/README.md b/examples/node/README.md index f95c086..0c6474a 100644 --- a/examples/node/README.md +++ b/examples/node/README.md @@ -11,7 +11,7 @@ Run it from the repository root: ```bash pnpm install pnpm build -OMS_PUBLISHABLE_KEY=your-publishable-key OMS_INDEXER_API_KEY=your-indexer-api-key OMS_PROJECT_ID=your-project-id pnpm dev:node-example +OMS_PUBLISHABLE_KEY=your-publishable-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/signInFlow.ts b/examples/node/signInFlow.ts index 395a46b..14bfd81 100644 --- a/examples/node/signInFlow.ts +++ b/examples/node/signInFlow.ts @@ -2,7 +2,6 @@ import readline from "node:readline/promises"; import {MemoryStorageManager, Networks, OMSClient} from "@0xsequence/typescript-sdk"; const publishableKey = requiredEnv("OMS_PUBLISHABLE_KEY", process.env.OMS_PUBLISHABLE_KEY); -const indexerApiKey = requiredEnv("OMS_INDEXER_API_KEY", process.env.OMS_INDEXER_API_KEY); const projectId = requiredEnv("OMS_PROJECT_ID", process.env.OMS_PROJECT_ID); async function main() { @@ -19,7 +18,6 @@ async function main() { const client = new OMSClient({ publishableKey, - indexerApiKey, projectId, storage: new MemoryStorageManager(), }); diff --git a/examples/react/.env.example b/examples/react/.env.example index 8289e05..6316db9 100644 --- a/examples/react/.env.example +++ b/examples/react/.env.example @@ -1,3 +1,2 @@ VITE_OMS_PUBLISHABLE_KEY=your-publishable-key -VITE_OMS_INDEXER_API_KEY=your-indexer-api-key VITE_OMS_PROJECT_ID=your-oms-project-id diff --git a/examples/react/README.md b/examples/react/README.md index 6cd9f30..aea350f 100644 --- a/examples/react/README.md +++ b/examples/react/README.md @@ -12,7 +12,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID pnpm dev:example ``` @@ -24,7 +24,7 @@ The example requires a publishable key and project ID. Configure them locally be ```bash cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID ``` The Amoy-only "ERC20 example" panel includes a WalletKit Dollar example using diff --git a/examples/react/src/config.ts b/examples/react/src/config.ts index 973a717..503b55b 100644 --- a/examples/react/src/config.ts +++ b/examples/react/src/config.ts @@ -2,7 +2,6 @@ export const PUBLISHABLE_KEY = requiredEnv( 'VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY, ) -export const INDEXER_API_KEY = requiredEnv('VITE_OMS_INDEXER_API_KEY', import.meta.env.VITE_OMS_INDEXER_API_KEY) export const PROJECT_ID = requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID) function requiredEnv(name: string, value: string | undefined): string { diff --git a/examples/react/src/omsClient.ts b/examples/react/src/omsClient.ts index ca2cec0..2b5c90d 100644 --- a/examples/react/src/omsClient.ts +++ b/examples/react/src/omsClient.ts @@ -1,10 +1,9 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { INDEXER_API_KEY, PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { PROJECT_ID, PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, - indexerApiKey: INDEXER_API_KEY, projectId: PROJECT_ID, }) diff --git a/examples/react/src/vite-env.d.ts b/examples/react/src/vite-env.d.ts index 80d9569..37fee95 100644 --- a/examples/react/src/vite-env.d.ts +++ b/examples/react/src/vite-env.d.ts @@ -2,7 +2,6 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string - readonly VITE_OMS_INDEXER_API_KEY?: string readonly VITE_OMS_PROJECT_ID?: string } diff --git a/examples/trails-actions/.env.example b/examples/trails-actions/.env.example index 8289e05..6316db9 100644 --- a/examples/trails-actions/.env.example +++ b/examples/trails-actions/.env.example @@ -1,3 +1,2 @@ VITE_OMS_PUBLISHABLE_KEY=your-publishable-key -VITE_OMS_INDEXER_API_KEY=your-indexer-api-key VITE_OMS_PROJECT_ID=your-oms-project-id diff --git a/examples/trails-actions/README.md b/examples/trails-actions/README.md index 99812a5..d096842 100644 --- a/examples/trails-actions/README.md +++ b/examples/trails-actions/README.md @@ -12,7 +12,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/trails-actions/.env.example examples/trails-actions/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID pnpm dev:trails-actions-example ``` diff --git a/examples/trails-actions/src/config.ts b/examples/trails-actions/src/config.ts index 444d19f..09ca60c 100644 --- a/examples/trails-actions/src/config.ts +++ b/examples/trails-actions/src/config.ts @@ -1,5 +1,4 @@ export const PUBLISHABLE_KEY = requiredEnv('VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY) -export const INDEXER_API_KEY = requiredEnv('VITE_OMS_INDEXER_API_KEY', import.meta.env.VITE_OMS_INDEXER_API_KEY) export const PROJECT_ID = requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID) function requiredEnv(name: string, value: string | undefined): string { diff --git a/examples/trails-actions/src/omsClient.ts b/examples/trails-actions/src/omsClient.ts index ca2cec0..2b5c90d 100644 --- a/examples/trails-actions/src/omsClient.ts +++ b/examples/trails-actions/src/omsClient.ts @@ -1,10 +1,9 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { INDEXER_API_KEY, PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { PROJECT_ID, PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, - indexerApiKey: INDEXER_API_KEY, projectId: PROJECT_ID, }) diff --git a/examples/trails-actions/src/trailsActions.ts b/examples/trails-actions/src/trailsActions.ts index 449a2d5..eb3578b 100644 --- a/examples/trails-actions/src/trailsActions.ts +++ b/examples/trails-actions/src/trailsActions.ts @@ -183,20 +183,18 @@ export function requirePreparedYieldTransactions( } export async function getPolygonBalances(walletAddress: Address): Promise { - const [polBalance, usdcResult] = await Promise.all([ - oms.indexer.getNativeTokenBalance({ - network: POLYGON_NETWORK, - walletAddress, - }), - oms.indexer.getTokenBalances({ - network: POLYGON_NETWORK, - contractAddress: POLYGON_USDC, - walletAddress, - includeMetadata: false, - }), - ]) + const balances = await oms.indexer.getBalances({ + networks: [POLYGON_NETWORK], + contractAddresses: [POLYGON_USDC], + walletAddress, + includeMetadata: false, + }) + const polBalance = balances.nativeBalances.find(balance => balance.chainId === POLYGON_NETWORK.id) + const usdcBalance = balances.balances.find(balance => + balance.contractAddress?.toLowerCase() === POLYGON_USDC.toLowerCase(), + ) const polRaw = polBalance?.balance ?? '0' - const usdcRaw = usdcResult.balances[0]?.balance ?? '0' + const usdcRaw = usdcBalance?.balance ?? '0' return { pol: formatTokenAmount(polRaw, 18, 'POL'), diff --git a/examples/trails-actions/src/vite-env.d.ts b/examples/trails-actions/src/vite-env.d.ts index 80d9569..37fee95 100644 --- a/examples/trails-actions/src/vite-env.d.ts +++ b/examples/trails-actions/src/vite-env.d.ts @@ -2,7 +2,6 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string - readonly VITE_OMS_INDEXER_API_KEY?: string readonly VITE_OMS_PROJECT_ID?: string } diff --git a/examples/wagmi/.env.example b/examples/wagmi/.env.example index 9347af6..9d0ccee 100644 --- a/examples/wagmi/.env.example +++ b/examples/wagmi/.env.example @@ -1,4 +1,3 @@ VITE_OMS_PUBLISHABLE_KEY=your-publishable-key -VITE_OMS_INDEXER_API_KEY=your-indexer-api-key VITE_OMS_PROJECT_ID=your-oms-project-id VITE_TRAILS_API_KEY=AQAAAAAAAMDoWz-avqIIjXGH7JJlBSormpo diff --git a/examples/wagmi/README.md b/examples/wagmi/README.md index 814bdf0..acccdb2 100644 --- a/examples/wagmi/README.md +++ b/examples/wagmi/README.md @@ -8,7 +8,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/wagmi/.env.example examples/wagmi/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY, VITE_OMS_INDEXER_API_KEY, and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID pnpm dev:wagmi-example ``` diff --git a/examples/wagmi/src/config.ts b/examples/wagmi/src/config.ts index 9e9907a..5f26fec 100644 --- a/examples/wagmi/src/config.ts +++ b/examples/wagmi/src/config.ts @@ -2,7 +2,6 @@ export const PUBLISHABLE_KEY = requiredEnv( 'VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY, ) -export const INDEXER_API_KEY = requiredEnv('VITE_OMS_INDEXER_API_KEY', import.meta.env.VITE_OMS_INDEXER_API_KEY) export const PROJECT_ID = requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID) export const TRAILS_API_KEY = import.meta.env.VITE_TRAILS_API_KEY diff --git a/examples/wagmi/src/omsClient.ts b/examples/wagmi/src/omsClient.ts index ca2cec0..2b5c90d 100644 --- a/examples/wagmi/src/omsClient.ts +++ b/examples/wagmi/src/omsClient.ts @@ -1,10 +1,9 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { INDEXER_API_KEY, PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { PROJECT_ID, PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, - indexerApiKey: INDEXER_API_KEY, projectId: PROJECT_ID, }) diff --git a/examples/wagmi/src/vite-env.d.ts b/examples/wagmi/src/vite-env.d.ts index 2225909..fd73e38 100644 --- a/examples/wagmi/src/vite-env.d.ts +++ b/examples/wagmi/src/vite-env.d.ts @@ -2,7 +2,6 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string - readonly VITE_OMS_INDEXER_API_KEY?: string readonly VITE_OMS_PROJECT_ID?: string readonly VITE_TRAILS_API_KEY?: string } diff --git a/packages/oms-wallet-wagmi-connector/README.md b/packages/oms-wallet-wagmi-connector/README.md index 81382fb..73e22c1 100644 --- a/packages/oms-wallet-wagmi-connector/README.md +++ b/packages/oms-wallet-wagmi-connector/README.md @@ -12,7 +12,6 @@ import { omsWalletConnector } from '@0xsequence/oms-wallet-wagmi-connector' const oms = new OMSClient({ publishableKey: import.meta.env.VITE_OMS_PUBLISHABLE_KEY, - indexerApiKey: import.meta.env.VITE_OMS_INDEXER_API_KEY, projectId: import.meta.env.VITE_OMS_PROJECT_ID, }) diff --git a/src/clients/indexerClient.ts b/src/clients/indexerClient.ts index c9c7633..7223128 100644 --- a/src/clients/indexerClient.ts +++ b/src/clients/indexerClient.ts @@ -1,14 +1,28 @@ -// Converted from Swift IndexerClient. +// Minimal hand-written IndexerGateway adapter for the SDK surface we expose. import {HttpClient} from "../httpClient.js"; import {errorMessage, OmsRequestError, OmsResponseError, type OmsUpstreamError} from "../errors.js"; import type {Network} from "../networks.js"; import {IndexerOperation} from "../operations.js"; +const WebrpcHeaderValue = "webrpc@v0.31.2;gen-typescript@v0.23.1;sequence-indexer@v0.4.0"; + +export type IndexerNetworkType = "MAINNETS" | "TESTNETS" | "ALL"; +export type ContractVerificationStatus = "VERIFIED" | "UNVERIFIED" | "ALL"; + export interface TokenBalancesPage { - page: number; - pageSize: number; - more: boolean; + page?: number; + column?: string; + before?: unknown; + after?: unknown; + sort?: SortBy[]; + pageSize?: number; + more?: boolean; +} + +export interface SortBy { + column: string; + order: "DESC" | "ASC"; } export interface TokenContractInfo { @@ -72,6 +86,8 @@ export interface TokenBalance { accountAddress?: string; /** Wire format uses `tokenID`; this field is re-mapped during decoding. */ tokenId?: string; + name?: string; + symbol?: string; balance?: string; balanceUSD?: string; priceUSD?: string; @@ -85,26 +101,106 @@ export interface TokenBalance { tokenMetadata?: TokenMetadata; } -export interface TokenBalancesResult { +export interface MetadataOptions { + verifiedOnly?: boolean; + unverifiedOnly?: boolean; + includeContracts?: string[]; +} + +export interface GetBalancesParams { + walletAddress: string; + networks?: Network[]; + networkType?: IndexerNetworkType; + contractAddresses?: string[]; + includeMetadata?: boolean; + omitPrices?: boolean; + tokenIds?: string[]; + contractStatus?: ContractVerificationStatus; + page?: TokenBalancesPage; +} + +export interface BalancesResult { status: number; page?: TokenBalancesPage; + nativeBalances: TokenBalance[]; balances: TokenBalance[]; } -interface NativeTokenBalancePayloadRaw { - balance?: NativeTokenBalanceRaw; +export interface TransactionTransfer { + transferType?: string; + contractAddress?: string; + contractType?: string; + from?: string; + to?: string; + tokenIds?: string[]; + amounts?: string[]; + logIndex?: number; + amountsUSD?: string[]; + pricesUSD?: string[]; + contractInfo?: TokenContractInfo; + tokenMetadata?: Record; } -interface NativeTokenBalanceRaw { - accountAddress?: string; - balance?: string; - balanceWei?: string; - chainId?: number; +export interface Transaction { + txnHash: string; + blockNumber: number; + blockHash: string; + chainId: number; + metaTxnId?: string; + transfers?: TransactionTransfer[]; + timestamp: string; +} + +export interface GetTransactionHistoryParams { + walletAddress: string; + networks?: Network[]; + networkType?: IndexerNetworkType; + contractAddresses?: string[]; + transactionHashes?: string[]; + metaTransactionIds?: string[]; + fromBlock?: number; + toBlock?: number; + tokenId?: string; + includeMetadata?: boolean; + omitPrices?: boolean; + metadataOptions?: MetadataOptions; + page?: TokenBalancesPage; } -interface TokenBalancesPayloadRaw { +export interface TransactionHistoryResult { + status: number; page?: TokenBalancesPage; - balances?: TokenBalanceRaw[]; + transactions: Transaction[]; +} + +interface GatewayNativeTokenBalances { + chainId: number; + errorReason?: string; + results?: NativeTokenBalanceRaw[]; +} + +interface GatewayTokenBalance { + chainId: number; + errorReason?: string; + results?: TokenBalanceRaw[]; +} + +interface GatewayTransaction { + chainId: number; + errorReason?: string; + results?: TransactionRaw[]; +} + +interface NativeTokenBalanceRaw { + accountAddress?: string; + chainId?: number; + name?: string; + symbol?: string; + balance?: string; + balanceUSD?: string; + priceUSD?: string; + priceUpdatedAt?: string; + errorReason?: string; } interface TokenBalanceRaw { @@ -136,102 +232,150 @@ interface TokenMetadataAssetRaw extends Omit { tokenID?: string; } -interface RequestPage { - page: number; - pageSize: number; - more: boolean; +interface TransactionRaw { + txnHash: string; + blockNumber: number; + blockHash: string; + chainId: number; + metaTxnID?: string; + transfers?: TransactionTransferRaw[]; + timestamp: string; } -interface TokenBalancesRequest { - page: RequestPage; - contractAddress?: string; - accountAddress: string; - includeMetadata: boolean; +interface TransactionTransferRaw extends Omit { + tokenIds?: string[]; + tokenIDs?: string[]; + tokenMetadata?: Record; +} + +interface TokenBalancesFilter { + accountAddresses: string[]; + contractStatus?: ContractVerificationStatus; + contractTypes?: string[]; + contractWhitelist?: string[]; + contractBlacklist?: string[]; + omitNativeBalances: boolean; + omitPrices?: boolean; + tokenIDs?: string[]; +} + +interface GetTokenBalancesDetailsRequest { + chainIds?: number[]; + networkType?: IndexerNetworkType; + filter: TokenBalancesFilter; + omitMetadata?: boolean; + page?: TokenBalancesPage; +} + +interface GetTokenBalancesDetailsResponse { + page?: TokenBalancesPage; + nativeBalances?: GatewayNativeTokenBalances[]; + balances?: GatewayTokenBalance[]; +} + +interface TransactionHistoryFilter { + accountAddresses: string[]; + contractAddresses?: string[]; + transactionHashes?: string[]; + metaTransactionIDs?: string[]; + fromBlock?: number; + toBlock?: number; + tokenID?: string; + omitPrices?: boolean; +} + +interface GetTransactionHistoryRequest { + chainIds?: number[]; + networkType?: IndexerNetworkType; + filter: TransactionHistoryFilter; + includeMetadata?: boolean; + metadataOptions?: MetadataOptions; + page?: TokenBalancesPage; +} + +interface GetTransactionHistoryResponse { + page?: TokenBalancesPage; + transactions?: GatewayTransaction[]; } // Matches the Swift `OmsEnvironment` shape used by IndexerClient. export interface OmsEnvironment { - indexerUrlTemplate: string; + indexerGatewayUrl: string; } export class IndexerClient { - private readonly indexerApiKey: string; + private readonly publishableKey: string; private readonly environment: OmsEnvironment; private readonly client: HttpClient; constructor(params: { - indexerApiKey: string, + publishableKey: string, environment: OmsEnvironment }) { - this.indexerApiKey = params.indexerApiKey; + this.publishableKey = params.publishableKey; this.environment = params.environment; this.client = new HttpClient(); } - async getTokenBalances(params: { - network: Network - contractAddress?: string - walletAddress: string - includeMetadata: boolean - page?: { - page?: number - pageSize?: number - } - }): Promise { - const request: TokenBalancesRequest = { - page: { - page: params.page?.page ?? 0, - pageSize: params.page?.pageSize ?? 40, - more: false, + async getBalances(params: GetBalancesParams): Promise { + const request: GetTokenBalancesDetailsRequest = { + ...this.chainScope(params), + filter: { + accountAddresses: [params.walletAddress], + contractWhitelist: nonEmpty(params.contractAddresses), + contractStatus: params.contractStatus, + omitNativeBalances: false, + omitPrices: params.omitPrices, + tokenIDs: nonEmpty(params.tokenIds), }, - accountAddress: params.walletAddress, - includeMetadata: params.includeMetadata, + omitMetadata: params.includeMetadata === false, + page: this.requestPage(params.page), }; - if (params.contractAddress) { - request.contractAddress = params.contractAddress; - } - const bodyString = JSON.stringify(request); - const baseUrl = this.indexerUrl(params.network); - - const response = await this.postJson(IndexerOperation.getTokenBalances, { - baseUrl, - path: "/GetTokenBalances", - body: bodyString, + const response = await this.postJson(IndexerOperation.getBalances, { + baseUrl: this.indexerGatewayUrl(), + path: "/GetTokenBalancesDetails", + body: JSON.stringify(request), headers: this.defaultHeaders(), }); return { status: response.statusCode, page: response.payload.page, - balances: (response.payload.balances ?? []).map(mapTokenBalance), + nativeBalances: flattenGatewayResults(response.payload.nativeBalances).map(mapNativeTokenBalance), + balances: flattenGatewayResults(response.payload.balances).map(mapTokenBalance), }; } - async getNativeTokenBalance(params: { - network: Network - walletAddress: string - }): Promise { - const response = await this.postJson(IndexerOperation.getNativeTokenBalance, { - baseUrl: this.indexerUrl(params.network), - path: "/GetNativeTokenBalance", - body: JSON.stringify({ accountAddress: params.walletAddress }), + async getTransactionHistory(params: GetTransactionHistoryParams): Promise { + const request: GetTransactionHistoryRequest = { + ...this.chainScope(params), + filter: { + accountAddresses: [params.walletAddress], + contractAddresses: nonEmpty(params.contractAddresses), + transactionHashes: nonEmpty(params.transactionHashes), + metaTransactionIDs: nonEmpty(params.metaTransactionIds), + fromBlock: params.fromBlock, + toBlock: params.toBlock, + tokenID: params.tokenId, + omitPrices: params.omitPrices, + }, + includeMetadata: params.includeMetadata ?? true, + metadataOptions: params.metadataOptions, + page: this.requestPage(params.page), + }; + + const response = await this.postJson(IndexerOperation.getTransactionHistory, { + baseUrl: this.indexerGatewayUrl(), + path: "/GetTransactionHistory", + body: JSON.stringify(request), headers: this.defaultHeaders(), }); - if (!response.payload.balance) { - return undefined; - } - return { - contractType: "NATIVE", - contractAddress: undefined, - accountAddress: response.payload.balance.accountAddress, - tokenId: undefined, - balance: response.payload.balance.balance ?? response.payload.balance.balanceWei, - blockHash: undefined, - blockNumber: undefined, - chainId: response.payload.balance.chainId, + status: response.statusCode, + page: response.payload.page, + transactions: flattenGatewayResults(response.payload.transactions).map(mapTransaction), }; } @@ -287,16 +431,78 @@ export class IndexerClient { return {statusCode: response.statusCode, payload}; } - private indexerUrl(network: Network): string { - return this.environment.indexerUrlTemplate.replace("{value}", network.name); + private chainScope(params: { + networks?: Network[] + networkType?: IndexerNetworkType + }): {chainIds?: number[], networkType?: IndexerNetworkType} { + if (params.networks && params.networks.length > 0) { + return {chainIds: params.networks.map(network => network.id)}; + } + return {networkType: params.networkType ?? "MAINNETS"}; } - private defaultHeaders(): Record { + private requestPage(page: TokenBalancesPage | undefined): TokenBalancesPage { return { - "X-Access-Key": this.indexerApiKey, + ...page, + page: page?.page ?? 0, + pageSize: page?.pageSize ?? 40, + }; + } + + private indexerGatewayUrl(): string { + return this.environment.indexerGatewayUrl; + } + + private defaultHeaders(): Record { + const headers: Record = { + "Api-Key": this.publishableKey, Accept: "application/json", + Webrpc: WebrpcHeaderValue, }; + + const origin = this.originHeader(); + if (origin) { + headers.Origin = origin; + } + + return headers; } + + private originHeader(): string | undefined { + if (typeof globalThis.location?.origin === "string") { + return undefined; + } + + // The dev IndexerGateway currently rejects originless publishable-key requests. + // Send the local dev origin from originless runtimes until the gateway accepts them. + return "http://localhost:5173"; + } +} + +function flattenGatewayResults(groups: Array<{results?: T[]}> | undefined): T[] { + return groups?.flatMap(group => group.results ?? []) ?? []; +} + +function nonEmpty(values: T[] | undefined): T[] | undefined { + return values && values.length > 0 ? values : undefined; +} + +function mapNativeTokenBalance(raw: NativeTokenBalanceRaw): TokenBalance { + return { + contractType: "NATIVE", + contractAddress: undefined, + accountAddress: raw.accountAddress, + tokenId: undefined, + name: raw.name, + symbol: raw.symbol, + balance: raw.balance, + balanceUSD: raw.balanceUSD, + priceUSD: raw.priceUSD, + priceUpdatedAt: raw.priceUpdatedAt, + blockHash: undefined, + blockNumber: undefined, + chainId: raw.chainId, + }; } /** Re-maps the wire key `tokenID` onto the camelCase `tokenId` field. */ @@ -320,6 +526,33 @@ function mapTokenBalance(raw: TokenBalanceRaw): TokenBalance { }; } +function mapTransaction(raw: TransactionRaw): Transaction { + return { + txnHash: raw.txnHash, + blockNumber: raw.blockNumber, + blockHash: raw.blockHash, + chainId: raw.chainId, + metaTxnId: raw.metaTxnID, + transfers: raw.transfers?.map(mapTransactionTransfer), + timestamp: raw.timestamp, + }; +} + +function mapTransactionTransfer(raw: TransactionTransferRaw): TransactionTransfer { + const {tokenIDs, tokenMetadata, ...transfer} = raw; + return { + ...transfer, + tokenIds: raw.tokenIds ?? tokenIDs, + tokenMetadata: tokenMetadata ? mapTokenMetadataRecord(tokenMetadata) : undefined, + }; +} + +function mapTokenMetadataRecord(raw: Record): Record { + return Object.fromEntries( + Object.entries(raw).map(([tokenId, metadata]) => [tokenId, mapTokenMetadata(metadata)]), + ); +} + function mapTokenMetadata(raw: TokenMetadataRaw): TokenMetadata { const {tokenID, assets, ...metadata} = raw; return { @@ -336,11 +569,9 @@ function mapTokenMetadata(raw: TokenMetadataRaw): TokenMetadata { } function responseErrorMessage(payload: unknown, operation: IndexerOperation, status: number): string { - if (payload && typeof payload === "object" && "message" in payload) { - const message = (payload as {message?: unknown}).message; - if (typeof message === "string" && message) { - return message; - } + const message = gatewayErrorMessage(payload); + if (message) { + return message; } return `${operation} failed with HTTP ${status}`; } @@ -369,11 +600,17 @@ function indexerResponseError(payload: unknown, status: number, fallbackMessage: service: "indexer", name: stringField(payload, "name") ?? stringField(payload, "error"), code: numberOrStringField(payload, "code"), - message: stringField(payload, "message") ?? fallbackMessage, + message: gatewayErrorMessage(payload) ?? fallbackMessage, status, }; } +function gatewayErrorMessage(payload: unknown): string | undefined { + return stringField(payload, "message") + ?? stringField(payload, "cause") + ?? stringField(payload, "msg"); +} + function stringField(source: unknown, key: string): string | undefined { const value = objectField(source, key); return typeof value === "string" ? value : undefined; diff --git a/src/clients/walletClient.ts b/src/clients/walletClient.ts index 5bbf6de..694e333 100644 --- a/src/clients/walletClient.ts +++ b/src/clients/walletClient.ts @@ -360,7 +360,6 @@ export class WalletClient { constructor(params: { publishableKey: string, - indexerApiKey: string, projectId: string, environment: Env, storage?: StorageManager @@ -414,7 +413,7 @@ export class WalletClient { createApiKeyFetch(params.publishableKey), ) this.indexerClient = new IndexerClient({ - indexerApiKey: params.indexerApiKey, + publishableKey: params.publishableKey, environment: params.environment, }) } @@ -1535,20 +1534,38 @@ export class WalletClient { throw new Error('No active wallet session') } - const nativeBalance = feeOptions.some(option => this.isNativeToken(option)) - ? await this.loadNativeTokenBalance(network, walletAddress) - : undefined - const contractAddresses = Array.from(new Set( feeOptions .map(option => this.normalizeAddress(option.token.contractAddress)) .filter((address): address is string => Boolean(address)), )) + const balances = await this.indexerClient.getBalances({ + networks: [network], + contractAddresses, + walletAddress, + includeMetadata: false, + }).catch(() => undefined) + const nativeBalance = feeOptions.some(option => this.isNativeToken(option)) + ? balances?.nativeBalances.find(balance => balance.chainId === network.id) + : undefined const tokenBalances = new Map( - await Promise.all(contractAddresses.map(async contractAddress => [ + contractAddresses.map(contractAddress => [ contractAddress, - await this.loadTokenBalanceOrZero(network, contractAddress, walletAddress), - ] as const)), + balances + ? balances.balances.find(balance => + this.normalizeAddress(balance.contractAddress) === contractAddress, + ) ?? { + contractType: 'ERC20', + contractAddress, + accountAddress: walletAddress, + tokenId: undefined, + balance: '0', + blockHash: undefined, + blockNumber: undefined, + chainId: network.id, + } + : undefined, + ]), ) return feeOptions.map(feeOption => { @@ -1568,40 +1585,6 @@ export class WalletClient { }) } - private async loadNativeTokenBalance( - network: Network, - walletAddress: Address, - ): Promise { - return this.indexerClient.getNativeTokenBalance({ - network, - walletAddress, - }).catch(() => undefined) - } - - private async loadTokenBalanceOrZero( - network: Network, - contractAddress: string, - walletAddress: Address, - ): Promise { - return this.indexerClient.getTokenBalances({ - network, - contractAddress, - walletAddress, - includeMetadata: false, - }).then(result => result.balances.find(balance => - this.normalizeAddress(balance.contractAddress) === contractAddress, - ) ?? { - contractType: 'ERC20', - contractAddress, - accountAddress: walletAddress, - tokenId: undefined, - balance: '0', - blockHash: undefined, - blockNumber: undefined, - chainId: network.id, - }).catch(() => undefined) - } - private defaultFeeOptionSelection( feeOptions: FeeOption[], ): FeeOptionSelection { diff --git a/src/index.ts b/src/index.ts index 2d67cde..8918acf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -73,12 +73,21 @@ export type { WalletSelectionBehavior, } from './clients/walletClient.js' export type { + BalancesResult, + ContractVerificationStatus, + GetBalancesParams, + GetTransactionHistoryParams, + IndexerNetworkType, + MetadataOptions, + SortBy, TokenContractInfo, TokenBalance, TokenBalancesPage, - TokenBalancesResult, TokenMetadata, TokenMetadataAsset, + Transaction, + TransactionHistoryResult, + TransactionTransfer, } from './clients/indexerClient.js' export type { AccessGrant, diff --git a/src/omsClient.ts b/src/omsClient.ts index 844a624..6aa124c 100644 --- a/src/omsClient.ts +++ b/src/omsClient.ts @@ -7,7 +7,6 @@ import {supportedNetworks} from "./networks.js"; interface OMSClientBaseParams { publishableKey: string; - indexerApiKey: string; projectId: string; storage?: StorageManager; redirectAuthStorage?: StorageManager; @@ -30,7 +29,6 @@ class OMSClientImpl { this.wallet = new WalletClient({ publishableKey: params.publishableKey, - indexerApiKey: params.indexerApiKey, projectId: params.projectId, environment, storage, @@ -39,7 +37,7 @@ class OMSClientImpl { }); this.indexer = new IndexerClient({ - indexerApiKey: params.indexerApiKey, + publishableKey: params.publishableKey, environment }); } diff --git a/src/omsEnvironment.ts b/src/omsEnvironment.ts index b1b641c..207636b 100644 --- a/src/omsEnvironment.ts +++ b/src/omsEnvironment.ts @@ -19,13 +19,13 @@ export interface OmsEnvironment< OidcProviders extends Record = Record, > { walletApiUrl: string; - indexerUrlTemplate: string; + indexerGatewayUrl: string; auth?: OmsAuthConfig; } export const defaultOmsEnvironment = { walletApiUrl: "https://sandbox-api.dev.polygon-dev.technology", - indexerUrlTemplate: "https://dev-{value}-indexer.sequence.app/rpc/Indexer/", + indexerGatewayUrl: "https://api.dev.polygon-dev.technology/v1/IndexerGateway/", auth: { oidcProviders: { google: googleOidcProvider(), diff --git a/src/operations.ts b/src/operations.ts index 1d16633..3493cf5 100644 --- a/src/operations.ts +++ b/src/operations.ts @@ -28,8 +28,8 @@ export const WalletOperation = { export type WalletOperation = typeof WalletOperation[keyof typeof WalletOperation] export const IndexerOperation = { - getTokenBalances: "indexer.getTokenBalances", - getNativeTokenBalance: "indexer.getNativeTokenBalance", + getBalances: "indexer.getBalances", + getTransactionHistory: "indexer.getTransactionHistory", } as const export type IndexerOperation = typeof IndexerOperation[keyof typeof IndexerOperation] diff --git a/tests/errorContracts.test.ts b/tests/errorContracts.test.ts index 6227e8a..5288b7f 100644 --- a/tests/errorContracts.test.ts +++ b/tests/errorContracts.test.ts @@ -1168,8 +1168,8 @@ describe("public API error contracts", () => { const oms = createOmsClient(); await expect(publicError(() => - oms.indexer.getTokenBalances({ - network: Networks.polygon, + oms.indexer.getBalances({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, }), @@ -1178,7 +1178,7 @@ describe("public API error contracts", () => { "code": "OMS_HTTP_ERROR", "message": "Indexer is unavailable", "name": "OmsRequestError", - "operation": "indexer.getTokenBalances", + "operation": "indexer.getBalances", "retryable": true, "status": 503, "txnId": null, @@ -1204,23 +1204,23 @@ describe("public API error contracts", () => { const oms = createOmsClient(); await expect(publicError(() => - oms.indexer.getTokenBalances({ - network: Networks.polygon, + oms.indexer.getBalances({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, }), )).resolves.toMatchInlineSnapshot(` { "code": "OMS_HTTP_ERROR", - "message": "indexer.getTokenBalances failed with HTTP 502", + "message": "indexer.getBalances failed with HTTP 502", "name": "OmsRequestError", - "operation": "indexer.getTokenBalances", + "operation": "indexer.getBalances", "retryable": true, "status": 502, "txnId": null, "upstreamError": { "code": null, - "message": "indexer.getTokenBalances failed with HTTP 502", + "message": "indexer.getBalances failed with HTTP 502", "name": null, "service": "indexer", "status": 502, @@ -1229,7 +1229,7 @@ describe("public API error contracts", () => { `); }); - it("snapshots native balance indexer errors with upstream details", async () => { + it("snapshots transaction history indexer errors with upstream details", async () => { let callCount = 0; vi.stubGlobal("fetch", vi.fn(async () => { callCount += 1; @@ -1249,16 +1249,16 @@ describe("public API error contracts", () => { const oms = createOmsClient(); await expect(publicErrors([ - ["indexer.getNativeTokenBalance.http", () => oms.indexer.getNativeTokenBalance({ - network: Networks.polygon, + ["indexer.getTransactionHistory.http", () => oms.indexer.getTransactionHistory({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", })], - ["indexer.getNativeTokenBalance.transport", () => oms.indexer.getNativeTokenBalance({ - network: Networks.polygon, + ["indexer.getTransactionHistory.transport", () => oms.indexer.getTransactionHistory({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", })], - ["indexer.getNativeTokenBalance.malformed", () => oms.indexer.getNativeTokenBalance({ - network: Networks.polygon, + ["indexer.getTransactionHistory.malformed", () => oms.indexer.getTransactionHistory({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", })], ])).resolves.toMatchInlineSnapshot(` @@ -1268,7 +1268,7 @@ describe("public API error contracts", () => { "code": "OMS_HTTP_ERROR", "message": "Indexer is unavailable", "name": "OmsRequestError", - "operation": "indexer.getNativeTokenBalance", + "operation": "indexer.getTransactionHistory", "retryable": true, "status": 503, "txnId": null, @@ -1280,14 +1280,14 @@ describe("public API error contracts", () => { "status": 503, }, }, - "label": "indexer.getNativeTokenBalance.http", + "label": "indexer.getTransactionHistory.http", }, { "error": { "code": "OMS_REQUEST_FAILED", "message": "fetch failed", "name": "OmsRequestError", - "operation": "indexer.getNativeTokenBalance", + "operation": "indexer.getTransactionHistory", "retryable": true, "status": null, "txnId": null, @@ -1299,26 +1299,26 @@ describe("public API error contracts", () => { "status": null, }, }, - "label": "indexer.getNativeTokenBalance.transport", + "label": "indexer.getTransactionHistory.transport", }, { "error": { "code": "OMS_INVALID_RESPONSE", - "message": "Invalid JSON response from indexer.getNativeTokenBalance", + "message": "Invalid JSON response from indexer.getTransactionHistory", "name": "OmsResponseError", - "operation": "indexer.getNativeTokenBalance", + "operation": "indexer.getTransactionHistory", "retryable": null, "status": 200, "txnId": null, "upstreamError": { "code": null, - "message": "Invalid JSON response from indexer.getNativeTokenBalance", + "message": "Invalid JSON response from indexer.getTransactionHistory", "name": null, "service": "indexer", "status": 200, }, }, - "label": "indexer.getNativeTokenBalance.malformed", + "label": "indexer.getTransactionHistory.malformed", }, ] `); @@ -1332,8 +1332,8 @@ describe("public API error contracts", () => { const oms = createOmsClient(); await expect(publicError(() => - oms.indexer.getTokenBalances({ - network: Networks.polygon, + oms.indexer.getBalances({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, }), @@ -1342,7 +1342,7 @@ describe("public API error contracts", () => { "code": "OMS_REQUEST_FAILED", "message": "fetch failed", "name": "OmsRequestError", - "operation": "indexer.getTokenBalances", + "operation": "indexer.getBalances", "retryable": true, "status": null, "txnId": null, @@ -1363,23 +1363,23 @@ describe("public API error contracts", () => { const oms = createOmsClient(); await expect(publicError(() => - oms.indexer.getTokenBalances({ - network: Networks.polygon, + oms.indexer.getBalances({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, }), )).resolves.toMatchInlineSnapshot(` { "code": "OMS_INVALID_RESPONSE", - "message": "Invalid JSON response from indexer.getTokenBalances", + "message": "Invalid JSON response from indexer.getBalances", "name": "OmsResponseError", - "operation": "indexer.getTokenBalances", + "operation": "indexer.getBalances", "retryable": null, "status": 200, "txnId": null, "upstreamError": { "code": null, - "message": "Invalid JSON response from indexer.getTokenBalances", + "message": "Invalid JSON response from indexer.getBalances", "name": null, "service": "indexer", "status": 200, @@ -1573,13 +1573,12 @@ function createOmsClient(params: { } = {}): OMSClient { const clientParams: ConstructorParameters[0] = { publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", storage: new MemoryStorageManager(), credentialSigner: params.credentialSigner ?? new MockSigner(), environment: { walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", }, }; diff --git a/tests/indexerClient.test.ts b/tests/indexerClient.test.ts index 7323437..0926f99 100644 --- a/tests/indexerClient.test.ts +++ b/tests/indexerClient.test.ts @@ -9,40 +9,63 @@ afterEach(() => { }); describe("IndexerClient", () => { - it("omits contractAddress when querying balances across contracts", async () => { + it("requests balances through IndexerGateway and flattens grouped chain results", async () => { const fetchMock = vi.fn(async () => new Response(JSON.stringify({ page: {page: 1, pageSize: 25, more: false}, + nativeBalances: [{ + chainId: 137, + results: [{ + accountAddress: "0x9999999999999999999999999999999999999999", + chainId: 137, + name: "POL", + symbol: "POL", + balance: "1000000000000000000", + balanceUSD: "0.20", + priceUSD: "0.20", + }], + }], 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, - }, + results: [{ + 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({ - indexerApiKey: "indexer-api-key", + publishableKey: "publishable-key", environment: testEnvironment(), }); - await expect(indexer.getTokenBalances({ - network: Networks.polygon, + await expect(indexer.getBalances({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: true, + contractAddresses: ["0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"], page: {page: 1, pageSize: 25}, })).resolves.toMatchObject({ status: 200, page: {page: 1, pageSize: 25, more: false}, + nativeBalances: [{ + contractType: "NATIVE", + symbol: "POL", + balance: "1000000000000000000", + balanceUSD: "0.20", + priceUSD: "0.20", + }], balances: [{ tokenId: "0", balance: "141799", @@ -55,11 +78,162 @@ describe("IndexerClient", () => { }], }); - expect(fetchMock.mock.calls[0][0].toString()).toBe("https://indexer.example/polygon/GetTokenBalances"); + expect(fetchMock.mock.calls[0][0].toString()).toBe("https://indexer.example/GetTokenBalancesDetails"); + expect(fetchMock.mock.calls[0][1]?.headers).toMatchObject({ + "Api-Key": "publishable-key", + Origin: "http://localhost:5173", + Webrpc: "webrpc@v0.31.2;gen-typescript@v0.23.1;sequence-indexer@v0.4.0", + }); expect(JSON.parse(fetchMock.mock.calls[0][1]?.body as string)).toEqual({ - page: {page: 1, pageSize: 25, more: false}, - accountAddress: "0x9999999999999999999999999999999999999999", + chainIds: [137], + filter: { + accountAddresses: ["0x9999999999999999999999999999999999999999"], + contractWhitelist: ["0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"], + omitNativeBalances: false, + }, + omitMetadata: false, + page: {page: 1, pageSize: 25}, + }); + }); + + it("defaults balance queries to mainnets when networks are omitted", async () => { + const fetchMock = vi.fn(async () => new Response(JSON.stringify({ + page: {page: 0, pageSize: 40, more: false}, + nativeBalances: [], + balances: [], + }), {status: 200})); + vi.stubGlobal("fetch", fetchMock); + + const indexer = new IndexerClient({ + publishableKey: "publishable-key", + environment: testEnvironment(), + }); + + await indexer.getBalances({ + walletAddress: "0x9999999999999999999999999999999999999999", + }); + + expect(JSON.parse(fetchMock.mock.calls[0][1]?.body as string)).toEqual({ + networkType: "MAINNETS", + filter: { + accountAddresses: ["0x9999999999999999999999999999999999999999"], + omitNativeBalances: false, + }, + omitMetadata: false, + page: {page: 0, pageSize: 40}, + }); + }); + + it("requests transaction history through IndexerGateway", async () => { + const fetchMock = vi.fn(async () => new Response(JSON.stringify({ + page: {page: 0, pageSize: 1, more: true}, + transactions: [{ + chainId: 1, + results: [{ + txnHash: "0xabc", + blockNumber: 123, + blockHash: "0xdef", + chainId: 1, + metaTxnID: "meta-1", + transfers: [{ + transferType: "RECEIVE", + contractAddress: "0x0000000000000000000000000000000000000000", + contractType: "NATIVE", + from: "0x1111111111111111111111111111111111111111", + to: "0x9999999999999999999999999999999999999999", + tokenIDs: ["0"], + amounts: ["1"], + logIndex: 0, + }], + timestamp: "2026-06-17T00:00:00Z", + }], + }], + }), {status: 200})); + vi.stubGlobal("fetch", fetchMock); + + const indexer = new IndexerClient({ + publishableKey: "publishable-key", + environment: testEnvironment(), + }); + + await expect(indexer.getTransactionHistory({ + networks: [Networks.mainnet], + walletAddress: "0x9999999999999999999999999999999999999999", + includeMetadata: true, + page: {pageSize: 1}, + })).resolves.toMatchObject({ + status: 200, + page: {page: 0, pageSize: 1, more: true}, + transactions: [{ + txnHash: "0xabc", + metaTxnId: "meta-1", + transfers: [{ + tokenIds: ["0"], + amounts: ["1"], + }], + }], + }); + + expect(fetchMock.mock.calls[0][0].toString()).toBe("https://indexer.example/GetTransactionHistory"); + expect(JSON.parse(fetchMock.mock.calls[0][1]?.body as string)).toEqual({ + chainIds: [1], + filter: { + accountAddresses: ["0x9999999999999999999999999999999999999999"], + }, includeMetadata: true, + page: {pageSize: 1, page: 0}, + }); + }); + + it("does not set a synthetic Origin when the runtime already has one", async () => { + const fetchMock = vi.fn(async () => new Response(JSON.stringify({ + page: {page: 0, pageSize: 40, more: false}, + nativeBalances: [], + balances: [], + }), {status: 200})); + vi.stubGlobal("fetch", fetchMock); + vi.stubGlobal("location", {origin: "http://app.example"}); + + const indexer = new IndexerClient({ + publishableKey: "publishable-key", + environment: testEnvironment(), + }); + + await indexer.getBalances({ + walletAddress: "0x9999999999999999999999999999999999999999", + }); + + expect(fetchMock.mock.calls[0][1]?.headers).not.toHaveProperty("Origin"); + }); + + it("preserves WebRPC gateway error causes", async () => { + vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ + error: "WebrpcEndpoint", + code: 0, + msg: "endpoint error", + cause: "InvalidCredentials 1003: omsx-api: invalid credentials, requestId: req_123", + status: 400, + }), {status: 400}))); + + const indexer = new IndexerClient({ + publishableKey: "publishable-key", + environment: testEnvironment(), + }); + + await expect(indexer.getBalances({ + walletAddress: "0x9999999999999999999999999999999999999999", + })).rejects.toMatchObject({ + code: "OMS_HTTP_ERROR", + message: "InvalidCredentials 1003: omsx-api: invalid credentials, requestId: req_123", + operation: "indexer.getBalances", + status: 400, + upstreamError: { + name: "WebrpcEndpoint", + code: 0, + message: "InvalidCredentials 1003: omsx-api: invalid credentials, requestId: req_123", + service: "indexer", + status: 400, + }, }); }); @@ -68,39 +242,39 @@ describe("IndexerClient", () => { vi.stubGlobal("fetch", fetchMock); const indexer = new IndexerClient({ - indexerApiKey: "indexer-api-key", + publishableKey: "publishable-key", environment: testEnvironment(), }); - await expect(indexer.getTokenBalances({ - network: Networks.polygon, - contractAddress: "0x2222222222222222222222222222222222222222", + await expect(indexer.getBalances({ + networks: [Networks.polygon], + contractAddresses: ["0x2222222222222222222222222222222222222222"], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, })).rejects.toMatchObject({ code: "OMS_INVALID_RESPONSE", - operation: "indexer.getTokenBalances", + operation: "indexer.getBalances", status: 200, }); - expect(fetchMock.mock.calls[0][0].toString()).toBe("https://indexer.example/polygon/GetTokenBalances"); + expect(fetchMock.mock.calls[0][0].toString()).toBe("https://indexer.example/GetTokenBalancesDetails"); }); 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({ - indexerApiKey: "indexer-api-key", + publishableKey: "publishable-key", environment: testEnvironment(), }); - await expect(indexer.getTokenBalances({ - network: Networks.polygon, - contractAddress: "0x2222222222222222222222222222222222222222", + await expect(indexer.getBalances({ + networks: [Networks.polygon], + contractAddresses: ["0x2222222222222222222222222222222222222222"], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, })).rejects.toMatchObject({ code: "OMS_HTTP_ERROR", - operation: "indexer.getTokenBalances", + operation: "indexer.getBalances", status: 502, retryable: true, }); @@ -111,6 +285,6 @@ function testEnvironment() { return { walletApiUrl: "https://wallet.example", apiRpcUrl: "https://api.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", }; } diff --git a/tests/networks.test.ts b/tests/networks.test.ts index 7101f50..a7ffbc5 100644 --- a/tests/networks.test.ts +++ b/tests/networks.test.ts @@ -51,7 +51,6 @@ describe("Networks", () => { it("is available from OMSClient", () => { const oms = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", }); diff --git a/tests/oidcRedirectAuth.test.ts b/tests/oidcRedirectAuth.test.ts index 92dbb91..99376f0 100644 --- a/tests/oidcRedirectAuth.test.ts +++ b/tests/oidcRedirectAuth.test.ts @@ -142,7 +142,6 @@ describe("WalletClient OIDC redirect auth", () => { const storage = new MemoryStorageManager(); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -177,7 +176,7 @@ describe("WalletClient OIDC redirect auth", () => { redirectAuthStorage: new MemoryStorageManager(), environment: defineOmsEnvironment({ walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", auth: { oidcProviders: { custom: { @@ -249,7 +248,7 @@ describe("WalletClient OIDC redirect auth", () => { projectId: "proj_custom", environment: defineOmsEnvironment({ walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", auth: { oidcProviders: { google: googleOidcProvider({ @@ -330,7 +329,7 @@ describe("WalletClient OIDC redirect auth", () => { redirectAuthStorage: new MemoryStorageManager(), environment: defineOmsEnvironment({ walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", auth: { oidcProviders: { custom: { @@ -811,7 +810,6 @@ function createWalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: params.projectId ?? "project-id", environment, storage: new MemoryStorageManager(), @@ -823,7 +821,7 @@ function createWalletClient { function createWalletWithSession(): WalletClient { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -122,6 +121,6 @@ function testCredential(seed = "11", isCaller = true) { function testEnvironment() { return { walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", }; } diff --git a/tests/walletErrors.test.ts b/tests/walletErrors.test.ts index ae04d66..72d2772 100644 --- a/tests/walletErrors.test.ts +++ b/tests/walletErrors.test.ts @@ -33,7 +33,6 @@ describe("WalletClient errors", () => { it("wraps local validation failures separately from request failures", async () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -55,7 +54,6 @@ describe("WalletClient errors", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -88,7 +86,6 @@ describe("WalletClient errors", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -128,6 +125,6 @@ function jsonResponse(body: unknown, status = 200): Response { function testEnvironment() { return { walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", }; } diff --git a/tests/walletSession.test.ts b/tests/walletSession.test.ts index 032cd29..509136c 100644 --- a/tests/walletSession.test.ts +++ b/tests/walletSession.test.ts @@ -52,7 +52,6 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), credentialSigner: new MockSigner(), @@ -71,7 +70,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -96,7 +94,6 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -156,7 +153,6 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -203,7 +199,6 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -240,7 +235,6 @@ describe("WalletClient session storage", () => { const nextOnSessionExpired = vi.fn(); const nextClient = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -275,7 +269,6 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -304,7 +297,6 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -336,7 +328,6 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -386,7 +377,6 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -416,7 +406,6 @@ describe("WalletClient session storage", () => { const onSessionExpired = vi.fn(); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -489,7 +478,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -534,7 +522,6 @@ describe("WalletClient session storage", () => { redirectAuthStorage.set(Constants.redirectAuthStorageKey, JSON.stringify({verifier: "old-verifier"})); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -595,7 +582,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -625,7 +611,6 @@ describe("WalletClient session storage", () => { const client = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -682,7 +667,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -742,7 +726,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -777,7 +760,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -845,7 +827,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -907,7 +888,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -963,7 +943,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1023,7 +1002,6 @@ describe("WalletClient session storage", () => { const storage = new MemoryStorageManager(); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -1092,7 +1070,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1135,7 +1112,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1191,7 +1167,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1244,7 +1219,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1290,7 +1264,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1342,7 +1315,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1394,7 +1366,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1446,7 +1417,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1478,7 +1448,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1520,7 +1489,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -1573,7 +1541,6 @@ describe("WalletClient session storage", () => { const storage = new MemoryStorageManager(); const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -1619,7 +1586,6 @@ describe("WalletClient session storage", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -1644,7 +1610,7 @@ describe("WalletClient session storage", () => { function testEnvironment() { return { walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", }; } diff --git a/tests/walletSigning.test.ts b/tests/walletSigning.test.ts index c524008..a536900 100644 --- a/tests/walletSigning.test.ts +++ b/tests/walletSigning.test.ts @@ -184,7 +184,6 @@ describe("WalletClient signing", () => { function createWalletWithSession(walletAddress: string): WalletClient { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -204,6 +203,6 @@ function jsonResponse(body: unknown): Response { function testEnvironment() { return { walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", }; } diff --git a/tests/walletTransactions.test.ts b/tests/walletTransactions.test.ts index 75863e9..5c4d0ad 100644 --- a/tests/walletTransactions.test.ts +++ b/tests/walletTransactions.test.ts @@ -82,34 +82,36 @@ describe("WalletClient transactions", () => { }); } - if (url.endsWith("/GetNativeTokenBalance")) { - expect(body).toEqual({ - accountAddress: "0x9999999999999999999999999999999999999999", - }); - return jsonResponse({ - balance: { - accountAddress: "0x9999999999999999999999999999999999999999", - balanceWei: "1000000000000000000", - chainId: 137, - }, - }); - } - - if (url.endsWith("/GetTokenBalances")) { + if (url.endsWith("/GetTokenBalancesDetails")) { expect(body).toMatchObject({ - contractAddress: "0x2222222222222222222222222222222222222222", - accountAddress: "0x9999999999999999999999999999999999999999", - includeMetadata: false, + chainIds: [137], + filter: { + accountAddresses: ["0x9999999999999999999999999999999999999999"], + contractWhitelist: ["0x2222222222222222222222222222222222222222"], + omitNativeBalances: false, + }, + omitMetadata: true, }); return jsonResponse({ page: {page: 0, pageSize: 40, more: false}, + nativeBalances: [{ + chainId: 137, + results: [{ + accountAddress: "0x9999999999999999999999999999999999999999", + balance: "1000000000000000000", + chainId: 137, + }], + }], balances: [{ - contractType: "ERC20", - contractAddress: "0x2222222222222222222222222222222222222222", - accountAddress: "0x9999999999999999999999999999999999999999", - tokenID: null, - balance: "2500000", chainId: 137, + results: [{ + contractType: "ERC20", + contractAddress: "0x2222222222222222222222222222222222222222", + accountAddress: "0x9999999999999999999999999999999999999999", + tokenID: null, + balance: "2500000", + chainId: 137, + }], }], }); } @@ -186,7 +188,7 @@ describe("WalletClient transactions", () => { }); } - if (url.endsWith("/GetNativeTokenBalance") || url.endsWith("/GetTokenBalances")) { + if (url.endsWith("/GetTokenBalancesDetails")) { throw new Error("default fee selection should not load balances"); } @@ -324,17 +326,30 @@ describe("WalletClient transactions", () => { }); } - if (url.endsWith("/GetTokenBalances")) { - const balance = body.contractAddress === daiAddress ? "100" : "2000"; + if (url.endsWith("/GetTokenBalancesDetails")) { + expect(body.filter.contractWhitelist).toEqual([daiAddress, usdcAddress]); return jsonResponse({ page: {page: 0, pageSize: 40, more: false}, balances: [{ - contractType: "ERC20", - contractAddress: body.contractAddress, - accountAddress: body.accountAddress, - tokenID: null, - balance, chainId: 137, + results: [ + { + contractType: "ERC20", + contractAddress: daiAddress, + accountAddress: "0x9999999999999999999999999999999999999999", + tokenID: null, + balance: "100", + chainId: 137, + }, + { + contractType: "ERC20", + contractAddress: usdcAddress, + accountAddress: "0x9999999999999999999999999999999999999999", + tokenID: null, + balance: "2000", + chainId: 137, + }, + ], }], }); } @@ -399,16 +414,19 @@ describe("WalletClient transactions", () => { }); } - if (url.endsWith("/GetTokenBalances")) { + if (url.endsWith("/GetTokenBalancesDetails")) { return jsonResponse({ page: {page: 0, pageSize: 40, more: false}, balances: [{ - contractType: "ERC20", - contractAddress: body.contractAddress, - accountAddress: body.accountAddress, - tokenID: null, - balance: "100", chainId: 137, + results: [{ + contractType: "ERC20", + contractAddress: usdcAddress, + accountAddress: "0x9999999999999999999999999999999999999999", + tokenID: null, + balance: "100", + chainId: 137, + }], }], }); } @@ -585,7 +603,6 @@ describe("WalletClient transactions", () => { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage: new MemoryStorageManager(), @@ -645,7 +662,6 @@ describe("WalletClient transactions", () => { function createWalletWithSession(storage: MemoryStorageManager, walletAddress: string): WalletClient { const wallet = new WalletClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: testEnvironment(), storage, @@ -665,6 +681,6 @@ function jsonResponse(body: unknown): Response { function testEnvironment() { return { walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", }; } diff --git a/type-tests/oidcProviderTypes.ts b/type-tests/oidcProviderTypes.ts index 1df13fa..cb1d827 100644 --- a/type-tests/oidcProviderTypes.ts +++ b/type-tests/oidcProviderTypes.ts @@ -13,18 +13,22 @@ import { type OmsSdkError, type OmsSdkErrorCode, type OmsUpstreamError, + type BalancesResult, + type GetBalancesParams, + type GetTransactionHistoryParams, + type IndexerNetworkType, type TokenBalance, type TokenBalancesPage, - type TokenBalancesResult, type TokenContractInfo, type TokenMetadata, + type TransactionHistoryResult, } from "../src/index"; import {defineOmsEnvironment, type OmsEnvironment} from "../src/omsEnvironment"; import {googleOidcProvider} from "../src/oidc"; const environment = defineOmsEnvironment({ walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", auth: { oidcProviders: { google: googleOidcProvider(), @@ -71,24 +75,20 @@ if (false) { const defaultClient = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", }); // @ts-expect-error publishableKey is required. -new OMSClient({indexerApiKey: "indexer-api-key", projectId: "project-id"}); -// @ts-expect-error indexerApiKey is required. -new OMSClient({publishableKey: "publishable-key", projectId: "project-id"}); +new OMSClient({projectId: "project-id"}); // @ts-expect-error projectId is required. -new OMSClient({publishableKey: "publishable-key", indexerApiKey: "indexer-api-key"}); +new OMSClient({publishableKey: "publishable-key"}); // @ts-expect-error old projectAccessKey initializer name is not supported. -new OMSClient({projectAccessKey: "publishable-key", indexerApiKey: "indexer-api-key", projectId: "project-id"}); +new OMSClient({projectAccessKey: "publishable-key", projectId: "project-id"}); // @ts-expect-error old publicApiKey initializer name is not supported. -new OMSClient({publicApiKey: "publishable-key", indexerApiKey: "indexer-api-key", projectId: "project-id"}); +new OMSClient({publicApiKey: "publishable-key", projectId: "project-id"}); // @ts-expect-error old authorizationScope initializer name is not supported. -new OMSClient({publishableKey: "publishable-key", indexerApiKey: "indexer-api-key", authorizationScope: "project-id"}); +new OMSClient({publishableKey: "publishable-key", authorizationScope: "project-id"}); new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", // @ts-expect-error session expiry is subscribed through wallet.onSessionExpired, not constructor params. onSessionExpired: () => {}, @@ -119,7 +119,14 @@ const tokenBalance: TokenBalance = { priceUSD: "1", }; const tokenBalancesPage: TokenBalancesPage = {page: 0, pageSize: 40, more: false}; -const tokenBalancesResult: TokenBalancesResult = {status: 200, page: tokenBalancesPage, balances: [tokenBalance]}; +const tokenBalancesResult: BalancesResult = { + status: 200, + page: tokenBalancesPage, + nativeBalances: [tokenBalance], + balances: [tokenBalance], +}; +const indexerNetworkType: IndexerNetworkType = "MAINNETS"; +const transactionHistoryResult: TransactionHistoryResult = {status: 200, page: tokenBalancesPage, transactions: []}; const upstreamError: OmsUpstreamError = { service: "waas", name: "CommitmentConsumed", @@ -140,52 +147,51 @@ void allNetworks; void tokenContractInfo; void tokenMetadata; void tokenBalancesResult; +void indexerNetworkType; +void transactionHistoryResult; void upstreamError; void maybeUpstreamError; void transactionExecutionCode; void defaultClient.supportedNetworks; // @ts-expect-error findNetworkById accepts numeric chain IDs only. findNetworkById("80002"); -void defaultClient.indexer.getTokenBalances({ - network: Networks.polygon, - contractAddress: "0x2222222222222222222222222222222222222222", +void defaultClient.indexer.getBalances({ + networks: [Networks.polygon], + contractAddresses: ["0x2222222222222222222222222222222222222222"], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, }); -void defaultClient.indexer.getTokenBalances({ - network: Networks.polygon, +void defaultClient.indexer.getBalances({ + networks: [Networks.polygon], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: true, page: {page: 1, pageSize: 25}, }); -void defaultClient.indexer.getTokenBalances({ +void defaultClient.indexer.getBalances({ // @ts-expect-error Indexer public methods accept Network objects, not numeric chain IDs. - network: 137, - contractAddress: "0x2222222222222222222222222222222222222222", + networks: [137], + contractAddresses: ["0x2222222222222222222222222222222222222222"], walletAddress: "0x9999999999999999999999999999999999999999", includeMetadata: false, }); -void defaultClient.indexer.getTokenBalances({ +void defaultClient.indexer.getBalances({ // @ts-expect-error chainId is not a public indexer parameter. chainId: 137, - contractAddress: "0x2222222222222222222222222222222222222222", + contractAddresses: ["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, +const getBalancesParams: GetBalancesParams = { walletAddress: "0x9999999999999999999999999999999999999999", -}); -void defaultClient.indexer.getNativeTokenBalance({ - // @ts-expect-error chainId is not a public indexer parameter. - chainId: 137, + networkType: "TESTNETS", +}; +void defaultClient.indexer.getBalances(getBalancesParams); +const transactionHistoryParams: GetTransactionHistoryParams = { walletAddress: "0x9999999999999999999999999999999999999999", -}); + networks: [Networks.polygon], + includeMetadata: true, +}; +void defaultClient.indexer.getTransactionHistory(transactionHistoryParams); void defaultClient.wallet.startOidcRedirectAuth({ provider: "google", redirectUri: "https://app.example/auth/callback", @@ -198,11 +204,10 @@ void defaultClient.wallet.startOidcRedirectAuth({ const customEnvironmentWithoutProviders = defineOmsEnvironment({ walletApiUrl: "https://wallet.example", - indexerUrlTemplate: "https://indexer.example/{value}", + indexerGatewayUrl: "https://indexer.example", }); const customClient = new OMSClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: customEnvironmentWithoutProviders, }); @@ -217,7 +222,6 @@ void customClient.wallet.startOidcRedirectAuth({ function createClient(params: { publishableKey: string; - indexerApiKey: string; projectId: string; environment?: OmsEnvironment; }) { @@ -226,12 +230,10 @@ function createClient(params: { void createClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", }); void createClient({ publishableKey: "publishable-key", - indexerApiKey: "indexer-api-key", projectId: "project-id", environment: customEnvironmentWithoutProviders, }); From d0543439e5c638c4a332e823a31741073ce73084 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Thu, 18 Jun 2026 14:09:50 +0300 Subject: [PATCH 4/9] Update generated WaaS client --- src/generated/waas.gen.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/generated/waas.gen.ts b/src/generated/waas.gen.ts index 6d30c21..9ef6a20 100644 --- a/src/generated/waas.gen.ts +++ b/src/generated/waas.gen.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// waas v1-26.5.25-cc1eed0 f038c851ab6eacfa03422c6c4ed3c069c28e78ea +// waas v1-26.6.17-061733f 2279592e720c50a33130cd3fd935b3e2e74ff67d // -- // Code generated by Webrpc-gen@v0.37.2 with typescript generator. DO NOT EDIT. // @@ -9,10 +9,10 @@ export const WebrpcVersion = "v1" // Schema version of your RIDL schema -export const WebrpcSchemaVersion = "v1-26.5.25-cc1eed0" +export const WebrpcSchemaVersion = "v1-26.6.17-061733f" // Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = "f038c851ab6eacfa03422c6c4ed3c069c28e78ea" +export const WebrpcSchemaHash = "2279592e720c50a33130cd3fd935b3e2e74ff67d" // // Client interface @@ -139,7 +139,7 @@ export interface FeeToken { symbol: string type: string decimals?: number - logoURL: string + logoURL?: string contractAddress?: string tokenID?: string } @@ -1432,7 +1432,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { export const WebrpcHeader = "Webrpc" -export const WebrpcHeaderValue = "webrpc@v0.37.2;gen-typescript@v0.26.0;waas@v1-26.5.25-cc1eed0" +export const WebrpcHeaderValue = "webrpc@v0.37.2;gen-typescript@v0.26.0;waas@v1-26.6.17-061733f" type WebrpcGenVersions = { WebrpcGenVersion: string; From d04bd07877df007d9dddbf241fdad56dbaf0f0d1 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Tue, 23 Jun 2026 11:53:35 +0300 Subject: [PATCH 5/9] Derive project ID from publishable key --- API.md | 3 --- README.md | 17 +++++------------ src/omsClient.ts | 15 +++++++++++++-- tests/errorContracts.test.ts | 3 +-- tests/networks.test.ts | 3 +-- tests/walletSession.test.ts | 18 ++++++----------- type-tests/oidcProviderTypes.ts | 34 ++++++++++++++------------------- 7 files changed, 40 insertions(+), 53 deletions(-) diff --git a/API.md b/API.md index fbac41f..190aec0 100644 --- a/API.md +++ b/API.md @@ -76,7 +76,6 @@ import { OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', - projectId: 'your-project-id', }) ``` @@ -85,7 +84,6 @@ const oms = new OMSClient({ ```typescript new OMSClient(params: { publishableKey: string - projectId: string environment?: OmsEnvironment storage?: StorageManager redirectAuthStorage?: StorageManager @@ -98,7 +96,6 @@ new OMSClient(params: { | Name | Type | Required | Description | |---|---|---|---| | `publishableKey` | `string` | Yes | Your OMS publishable key. Wallet and IndexerGateway requests send this as the `Api-Key` header. | -| `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. | diff --git a/README.md b/README.md index 696f96c..a9fcdb1 100644 --- a/README.md +++ b/README.md @@ -17,20 +17,19 @@ npm install @0xsequence/typescript-sdk yarn add @0xsequence/typescript-sdk ``` -Then initialize the client with your OMS project credentials: +Then initialize the client with your OMS publishable key: ```typescript import { OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', - projectId: 'your-project-id', }) ``` By default, wallet requests use the sandbox WaaS API at `https://sandbox-api.dev.polygon-dev.technology`. -In Vite browser apps, keep those values in local environment variables: +In Vite browser apps, keep that value in local environment variables: ```typescript function requiredEnv(name: string, value: string | undefined): string { @@ -42,7 +41,6 @@ function requiredEnv(name: string, value: string | undefined): string { const oms = new OMSClient({ publishableKey: requiredEnv('VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY), - projectId: requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID), }) ``` @@ -69,7 +67,7 @@ To run it locally from the repository root: ```bash cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/react/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY in examples/react/.env.local pnpm dev:example ``` @@ -95,7 +93,7 @@ To run it locally from the repository root: ```bash cp examples/wagmi/.env.example examples/wagmi/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/wagmi/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY in examples/wagmi/.env.local pnpm dev:wagmi-example ``` @@ -109,7 +107,7 @@ To run it locally from the repository root: ```bash cp examples/trails-actions/.env.example examples/trails-actions/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID in examples/trails-actions/.env.local +# Fill VITE_OMS_PUBLISHABLE_KEY in examples/trails-actions/.env.local pnpm dev:trails-actions-example ``` @@ -121,7 +119,6 @@ import { parseUnits } from 'viem' const oms = new OMSClient({ publishableKey: 'your-publishable-key', - projectId: 'your-project-id', }) // 1. Send a one-time code to the user's email @@ -188,7 +185,6 @@ Google redirect auth is configured on the default environment. The redirect auth ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', - projectId: 'your-project-id', }) ``` @@ -243,7 +239,6 @@ The SDK makes expired sessions inactive before protected wallet operations and t ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', - projectId: 'your-project-id', }) const unsubscribe = oms.wallet.onSessionExpired(({ session }) => { @@ -423,7 +418,6 @@ const tx = await oms.wallet.sendTransaction({ ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', - projectId: 'your-project-id', environment: { walletApiUrl: 'https://staging-wallet.example.com', indexerGatewayUrl: 'https://staging-indexer.example.com/v1/IndexerGateway/', @@ -442,7 +436,6 @@ import { MemoryStorageManager, OMSClient } from '@0xsequence/typescript-sdk' const oms = new OMSClient({ publishableKey: 'your-publishable-key', - projectId: 'your-project-id', storage: new MemoryStorageManager(), }) ``` diff --git a/src/omsClient.ts b/src/omsClient.ts index 6aa124c..dad4943 100644 --- a/src/omsClient.ts +++ b/src/omsClient.ts @@ -4,10 +4,10 @@ import {createDefaultStorage, StorageManager} from "./storageManager.js"; import {IndexerClient} from "./clients/indexerClient.js"; import type {CredentialSigner} from "./credentialSigner.js"; import {supportedNetworks} from "./networks.js"; +import {OmsValidationError} from "./errors.js"; interface OMSClientBaseParams { publishableKey: string; - projectId: string; storage?: StorageManager; redirectAuthStorage?: StorageManager; credentialSigner?: CredentialSigner; @@ -26,10 +26,11 @@ class OMSClientImpl { constructor(params: OMSClientBaseParams & {environment?: Env}) { const environment = (params.environment ?? defaultOmsEnvironment) as Env; const storage = params.storage ?? createDefaultStorage() + const projectId = projectIdFromPublishableKey(params.publishableKey) this.wallet = new WalletClient({ publishableKey: params.publishableKey, - projectId: params.projectId, + projectId, environment, storage, redirectAuthStorage: params.redirectAuthStorage, @@ -52,3 +53,13 @@ interface OMSClientConstructor { } export const OMSClient: OMSClientConstructor = OMSClientImpl as unknown as OMSClientConstructor; + +function projectIdFromPublishableKey(publishableKey: string): string { + const match = /^pk_[^_]+_[^_]+_([^_]+)_[^_]+$/.exec(publishableKey); + if (!match) { + throw new OmsValidationError({ + message: "Invalid publishableKey.", + }); + } + return `prj_${match[1]}`; +} diff --git a/tests/errorContracts.test.ts b/tests/errorContracts.test.ts index 5288b7f..cafb29c 100644 --- a/tests/errorContracts.test.ts +++ b/tests/errorContracts.test.ts @@ -1572,8 +1572,7 @@ function createOmsClient(params: { redirectAuthStorage?: MemoryStorageManager | null } = {}): OMSClient { const clientParams: ConstructorParameters[0] = { - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", storage: new MemoryStorageManager(), credentialSigner: params.credentialSigner ?? new MockSigner(), environment: { diff --git a/tests/networks.test.ts b/tests/networks.test.ts index a7ffbc5..6dd7044 100644 --- a/tests/networks.test.ts +++ b/tests/networks.test.ts @@ -50,8 +50,7 @@ describe("Networks", () => { it("is available from OMSClient", () => { const oms = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", }); expect(oms.supportedNetworks).toBe(supportedNetworks); diff --git a/tests/walletSession.test.ts b/tests/walletSession.test.ts index 509136c..4d2a78b 100644 --- a/tests/walletSession.test.ts +++ b/tests/walletSession.test.ts @@ -51,8 +51,7 @@ describe("WalletClient session storage", () => { vi.stubGlobal("localStorage", undefined); const client = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", environment: testEnvironment(), credentialSigner: new MockSigner(), }); @@ -198,8 +197,7 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", environment: testEnvironment(), storage, credentialSigner: signer, @@ -234,8 +232,7 @@ describe("WalletClient session storage", () => { const nextOnSessionExpired = vi.fn(); const nextClient = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", environment: testEnvironment(), storage, credentialSigner: signer, @@ -268,8 +265,7 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", environment: testEnvironment(), storage, credentialSigner: signer, @@ -296,8 +292,7 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", environment: testEnvironment(), storage, credentialSigner: signer, @@ -610,8 +605,7 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", environment: testEnvironment(), storage, credentialSigner: new MockSigner(), diff --git a/type-tests/oidcProviderTypes.ts b/type-tests/oidcProviderTypes.ts index cb1d827..30a4b2d 100644 --- a/type-tests/oidcProviderTypes.ts +++ b/type-tests/oidcProviderTypes.ts @@ -74,22 +74,20 @@ if (false) { } const defaultClient = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", }); // @ts-expect-error publishableKey is required. -new OMSClient({projectId: "project-id"}); -// @ts-expect-error projectId is required. -new OMSClient({publishableKey: "publishable-key"}); -// @ts-expect-error old projectAccessKey initializer name is not supported. -new OMSClient({projectAccessKey: "publishable-key", projectId: "project-id"}); -// @ts-expect-error old publicApiKey initializer name is not supported. -new OMSClient({publicApiKey: "publishable-key", projectId: "project-id"}); -// @ts-expect-error old authorizationScope initializer name is not supported. -new OMSClient({publishableKey: "publishable-key", authorizationScope: "project-id"}); +new OMSClient({}); +// @ts-expect-error projectId is not a constructor parameter. +new OMSClient({publishableKey: "pk_test_sdbx_project_key", projectId: "project-id"}); +// @ts-expect-error projectAccessKey initializer name is not supported. +new OMSClient({projectAccessKey: "pk_test_sdbx_project_key"}); +// @ts-expect-error publicApiKey initializer name is not supported. +new OMSClient({publicApiKey: "pk_test_sdbx_project_key"}); +// @ts-expect-error authorizationScope initializer name is not supported. +new OMSClient({publishableKey: "pk_test_sdbx_project_key", authorizationScope: "project-id"}); new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", // @ts-expect-error session expiry is subscribed through wallet.onSessionExpired, not constructor params. onSessionExpired: () => {}, }); @@ -207,8 +205,7 @@ const customEnvironmentWithoutProviders = defineOmsEnvironment({ indexerGatewayUrl: "https://indexer.example", }); const customClient = new OMSClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", environment: customEnvironmentWithoutProviders, }); let broadlyTypedClient: OMSClient; @@ -222,18 +219,15 @@ void customClient.wallet.startOidcRedirectAuth({ function createClient(params: { publishableKey: string; - projectId: string; environment?: OmsEnvironment; }) { return new OMSClient(params); } void createClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", }); void createClient({ - publishableKey: "publishable-key", - projectId: "project-id", + publishableKey: "pk_test_sdbx_project_key", environment: customEnvironmentWithoutProviders, }); From 8196fb06caab36c22db71870470c89f4657f4071 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Tue, 23 Jun 2026 11:53:48 +0300 Subject: [PATCH 6/9] Update examples for publishable key config --- .github/workflows/pages.yml | 3 --- .github/workflows/tests.yml | 9 +++------ AGENTS.md | 6 +++--- examples/node-contract-deploy-example/.env.example | 1 - examples/node-contract-deploy-example/README.md | 2 +- examples/node-contract-deploy-example/deployErc20.ts | 2 -- examples/node/README.md | 2 +- examples/node/signInFlow.ts | 2 -- examples/react/.env.example | 1 - examples/react/README.md | 6 +++--- examples/react/src/config.ts | 1 - examples/react/src/omsClient.ts | 3 +-- examples/react/src/vite-env.d.ts | 1 - examples/trails-actions/.env.example | 1 - examples/trails-actions/README.md | 2 +- examples/trails-actions/src/config.ts | 1 - examples/trails-actions/src/omsClient.ts | 3 +-- examples/trails-actions/src/vite-env.d.ts | 1 - examples/wagmi/.env.example | 1 - examples/wagmi/README.md | 2 +- examples/wagmi/src/config.ts | 1 - examples/wagmi/src/omsClient.ts | 3 +-- examples/wagmi/src/vite-env.d.ts | 1 - packages/oms-wallet-wagmi-connector/README.md | 1 - 24 files changed, 16 insertions(+), 40 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 794e734..76b6bb3 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -47,21 +47,18 @@ jobs: env: GITHUB_PAGES: 'true' VITE_OMS_PUBLISHABLE_KEY: ${{ secrets.OMS_PUBLISHABLE_KEY }} - VITE_OMS_PROJECT_ID: ${{ secrets.OMS_PROJECT_ID }} - name: Build Trails Actions example run: pnpm build:trails-actions-example env: GITHUB_PAGES: 'true' VITE_OMS_PUBLISHABLE_KEY: ${{ secrets.OMS_PUBLISHABLE_KEY }} - VITE_OMS_PROJECT_ID: ${{ secrets.OMS_PROJECT_ID }} - name: Build Wagmi example run: pnpm build:wagmi-example env: GITHUB_PAGES: 'true' VITE_OMS_PUBLISHABLE_KEY: ${{ secrets.OMS_PUBLISHABLE_KEY }} - VITE_OMS_PROJECT_ID: ${{ secrets.OMS_PROJECT_ID }} VITE_TRAILS_API_KEY: ${{ secrets.VITE_TRAILS_API_KEY }} - name: Stage Pages artifact diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e3178ec..bdd7ffc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,18 +52,15 @@ jobs: - name: Build React example run: pnpm build:example env: - VITE_OMS_PUBLISHABLE_KEY: ci-publishable-key - VITE_OMS_PROJECT_ID: ci-project-id + VITE_OMS_PUBLISHABLE_KEY: pk_ci_sdbx_ciproject_cikey - name: Build Trails Actions example run: pnpm build:trails-actions-example env: - VITE_OMS_PUBLISHABLE_KEY: ci-publishable-key - VITE_OMS_PROJECT_ID: ci-project-id + VITE_OMS_PUBLISHABLE_KEY: pk_ci_sdbx_ciproject_cikey - name: Build Wagmi example run: pnpm --filter wagmi-example build env: - VITE_OMS_PUBLISHABLE_KEY: ci-publishable-key - VITE_OMS_PROJECT_ID: ci-project-id + VITE_OMS_PUBLISHABLE_KEY: pk_ci_sdbx_ciproject_cikey VITE_TRAILS_API_KEY: ci-trails-key diff --git a/AGENTS.md b/AGENTS.md index c5dedfe..2631326 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -163,10 +163,10 @@ execution commands. ## Security and Configuration - Do not commit real secrets. `.env.local` and `.env.*.local` files are ignored for local overrides. -- The React example uses `examples/react/.env.example` for `VITE_OMS_PUBLISHABLE_KEY` and `VITE_OMS_PROJECT_ID`; keep local overrides in `examples/react/.env.local`. -- The wagmi React example uses `examples/wagmi/.env.example` for `VITE_OMS_PUBLISHABLE_KEY` and `VITE_OMS_PROJECT_ID`; keep local overrides in `examples/wagmi/.env.local`. +- The React example uses `examples/react/.env.example` for `VITE_OMS_PUBLISHABLE_KEY`; keep local overrides in `examples/react/.env.local`. +- The wagmi React example uses `examples/wagmi/.env.example` for `VITE_OMS_PUBLISHABLE_KEY`; keep local overrides in `examples/wagmi/.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. -- GitHub Pages reads `OMS_PUBLISHABLE_KEY` and `OMS_PROJECT_ID` for deployed examples. The wagmi example also reads `VITE_TRAILS_API_KEY`. Do not require those secrets for ordinary local unit tests unless the test explicitly needs an external boundary. +- GitHub Pages reads `OMS_PUBLISHABLE_KEY` for deployed examples. The wagmi example also reads `VITE_TRAILS_API_KEY`. Do not require those secrets for ordinary local unit tests unless the test explicitly needs an external boundary. ## Agent Workflow Rules diff --git a/examples/node-contract-deploy-example/.env.example b/examples/node-contract-deploy-example/.env.example index 08c0ba0..f2e1dbd 100644 --- a/examples/node-contract-deploy-example/.env.example +++ b/examples/node-contract-deploy-example/.env.example @@ -1,3 +1,2 @@ OMS_PUBLISHABLE_KEY=your-publishable-key -OMS_PROJECT_ID=your-oms-project-id # DEPLOY_SALT=0x0000000000000000000000000000000000000000000000000000000000000001 diff --git a/examples/node-contract-deploy-example/README.md b/examples/node-contract-deploy-example/README.md index fd5a793..f8908cb 100644 --- a/examples/node-contract-deploy-example/README.md +++ b/examples/node-contract-deploy-example/README.md @@ -32,7 +32,7 @@ From the repository root: pnpm install pnpm build cp examples/node-contract-deploy-example/.env.example examples/node-contract-deploy-example/.env.local -# Fill OMS_PUBLISHABLE_KEY and OMS_PROJECT_ID in .env.local +# Fill OMS_PUBLISHABLE_KEY in .env.local pnpm dev:node-contract-deploy-example ``` diff --git a/examples/node-contract-deploy-example/deployErc20.ts b/examples/node-contract-deploy-example/deployErc20.ts index d71f84b..a372055 100644 --- a/examples/node-contract-deploy-example/deployErc20.ts +++ b/examples/node-contract-deploy-example/deployErc20.ts @@ -14,7 +14,6 @@ loadDotenv({path: join(exampleDir, ".env.local"), quiet: true}); loadDotenv({path: join(exampleDir, ".env"), quiet: true}); const publishableKey = requiredEnv("OMS_PUBLISHABLE_KEY", process.env.OMS_PUBLISHABLE_KEY); -const projectId = requiredEnv("OMS_PROJECT_ID", process.env.OMS_PROJECT_ID); const defaultDeployerAddress = "0xce0042B868300000d44A59004Da54A005ffdcf9f" as const satisfies Address; const deployerAddress = optionalAddress("DEPLOYER_ADDRESS", process.env.DEPLOYER_ADDRESS) ?? defaultDeployerAddress; @@ -45,7 +44,6 @@ async function main() { const client = new OMSClient({ publishableKey, - projectId, storage: new MemoryStorageManager(), }); diff --git a/examples/node/README.md b/examples/node/README.md index 0c6474a..3946e97 100644 --- a/examples/node/README.md +++ b/examples/node/README.md @@ -11,7 +11,7 @@ Run it from the repository root: ```bash pnpm install pnpm build -OMS_PUBLISHABLE_KEY=your-publishable-key OMS_PROJECT_ID=your-project-id pnpm dev:node-example +OMS_PUBLISHABLE_KEY=your-publishable-key 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/signInFlow.ts b/examples/node/signInFlow.ts index 14bfd81..b426923 100644 --- a/examples/node/signInFlow.ts +++ b/examples/node/signInFlow.ts @@ -2,7 +2,6 @@ import readline from "node:readline/promises"; import {MemoryStorageManager, Networks, OMSClient} from "@0xsequence/typescript-sdk"; const publishableKey = requiredEnv("OMS_PUBLISHABLE_KEY", process.env.OMS_PUBLISHABLE_KEY); -const projectId = requiredEnv("OMS_PROJECT_ID", process.env.OMS_PROJECT_ID); async function main() { console.log("------------------------------------------------------------"); @@ -18,7 +17,6 @@ async function main() { const client = new OMSClient({ publishableKey, - projectId, storage: new MemoryStorageManager(), }); diff --git a/examples/react/.env.example b/examples/react/.env.example index 6316db9..a92c198 100644 --- a/examples/react/.env.example +++ b/examples/react/.env.example @@ -1,2 +1 @@ VITE_OMS_PUBLISHABLE_KEY=your-publishable-key -VITE_OMS_PROJECT_ID=your-oms-project-id diff --git a/examples/react/README.md b/examples/react/README.md index aea350f..b3cd483 100644 --- a/examples/react/README.md +++ b/examples/react/README.md @@ -12,7 +12,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY pnpm dev:example ``` @@ -20,11 +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 requires a publishable key and project ID. Configure them locally before running the dev server: +The example requires a publishable key. Configure it locally before running the dev server: ```bash cp examples/react/.env.example examples/react/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY ``` The Amoy-only "ERC20 example" panel includes a WalletKit Dollar example using diff --git a/examples/react/src/config.ts b/examples/react/src/config.ts index 503b55b..895ba33 100644 --- a/examples/react/src/config.ts +++ b/examples/react/src/config.ts @@ -2,7 +2,6 @@ export const PUBLISHABLE_KEY = requiredEnv( 'VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY, ) -export 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) { diff --git a/examples/react/src/omsClient.ts b/examples/react/src/omsClient.ts index 2b5c90d..36a1e3c 100644 --- a/examples/react/src/omsClient.ts +++ b/examples/react/src/omsClient.ts @@ -1,9 +1,8 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, - projectId: PROJECT_ID, }) diff --git a/examples/react/src/vite-env.d.ts b/examples/react/src/vite-env.d.ts index 37fee95..c848853 100644 --- a/examples/react/src/vite-env.d.ts +++ b/examples/react/src/vite-env.d.ts @@ -2,7 +2,6 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string - readonly VITE_OMS_PROJECT_ID?: string } interface ImportMeta { diff --git a/examples/trails-actions/.env.example b/examples/trails-actions/.env.example index 6316db9..a92c198 100644 --- a/examples/trails-actions/.env.example +++ b/examples/trails-actions/.env.example @@ -1,2 +1 @@ VITE_OMS_PUBLISHABLE_KEY=your-publishable-key -VITE_OMS_PROJECT_ID=your-oms-project-id diff --git a/examples/trails-actions/README.md b/examples/trails-actions/README.md index d096842..53b411e 100644 --- a/examples/trails-actions/README.md +++ b/examples/trails-actions/README.md @@ -12,7 +12,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/trails-actions/.env.example examples/trails-actions/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY pnpm dev:trails-actions-example ``` diff --git a/examples/trails-actions/src/config.ts b/examples/trails-actions/src/config.ts index 09ca60c..dcba8f9 100644 --- a/examples/trails-actions/src/config.ts +++ b/examples/trails-actions/src/config.ts @@ -1,5 +1,4 @@ export const PUBLISHABLE_KEY = requiredEnv('VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY) -export 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) { diff --git a/examples/trails-actions/src/omsClient.ts b/examples/trails-actions/src/omsClient.ts index 2b5c90d..36a1e3c 100644 --- a/examples/trails-actions/src/omsClient.ts +++ b/examples/trails-actions/src/omsClient.ts @@ -1,9 +1,8 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, - projectId: PROJECT_ID, }) diff --git a/examples/trails-actions/src/vite-env.d.ts b/examples/trails-actions/src/vite-env.d.ts index 37fee95..c848853 100644 --- a/examples/trails-actions/src/vite-env.d.ts +++ b/examples/trails-actions/src/vite-env.d.ts @@ -2,7 +2,6 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string - readonly VITE_OMS_PROJECT_ID?: string } interface ImportMeta { diff --git a/examples/wagmi/.env.example b/examples/wagmi/.env.example index 9d0ccee..3b43dff 100644 --- a/examples/wagmi/.env.example +++ b/examples/wagmi/.env.example @@ -1,3 +1,2 @@ VITE_OMS_PUBLISHABLE_KEY=your-publishable-key -VITE_OMS_PROJECT_ID=your-oms-project-id VITE_TRAILS_API_KEY=AQAAAAAAAMDoWz-avqIIjXGH7JJlBSormpo diff --git a/examples/wagmi/README.md b/examples/wagmi/README.md index acccdb2..fce64ec 100644 --- a/examples/wagmi/README.md +++ b/examples/wagmi/README.md @@ -8,7 +8,7 @@ Run it from the repository root: pnpm install pnpm build cp examples/wagmi/.env.example examples/wagmi/.env.local -# Fill VITE_OMS_PUBLISHABLE_KEY and VITE_OMS_PROJECT_ID +# Fill VITE_OMS_PUBLISHABLE_KEY pnpm dev:wagmi-example ``` diff --git a/examples/wagmi/src/config.ts b/examples/wagmi/src/config.ts index 5f26fec..9c05a77 100644 --- a/examples/wagmi/src/config.ts +++ b/examples/wagmi/src/config.ts @@ -2,7 +2,6 @@ export const PUBLISHABLE_KEY = requiredEnv( 'VITE_OMS_PUBLISHABLE_KEY', import.meta.env.VITE_OMS_PUBLISHABLE_KEY, ) -export const PROJECT_ID = requiredEnv('VITE_OMS_PROJECT_ID', import.meta.env.VITE_OMS_PROJECT_ID) export const TRAILS_API_KEY = import.meta.env.VITE_TRAILS_API_KEY function requiredEnv(name: string, value: string | undefined): string { diff --git a/examples/wagmi/src/omsClient.ts b/examples/wagmi/src/omsClient.ts index 2b5c90d..36a1e3c 100644 --- a/examples/wagmi/src/omsClient.ts +++ b/examples/wagmi/src/omsClient.ts @@ -1,9 +1,8 @@ import { OMSClient } from '@0xsequence/typescript-sdk' -import { PROJECT_ID, PUBLISHABLE_KEY } from './config' +import { PUBLISHABLE_KEY } from './config' export const TEST_SESSION_LIFETIME_SECONDS = 604_800 export const oms = new OMSClient({ publishableKey: PUBLISHABLE_KEY, - projectId: PROJECT_ID, }) diff --git a/examples/wagmi/src/vite-env.d.ts b/examples/wagmi/src/vite-env.d.ts index fd73e38..927a9e0 100644 --- a/examples/wagmi/src/vite-env.d.ts +++ b/examples/wagmi/src/vite-env.d.ts @@ -2,6 +2,5 @@ interface ImportMetaEnv { readonly VITE_OMS_PUBLISHABLE_KEY?: string - readonly VITE_OMS_PROJECT_ID?: string readonly VITE_TRAILS_API_KEY?: string } diff --git a/packages/oms-wallet-wagmi-connector/README.md b/packages/oms-wallet-wagmi-connector/README.md index 73e22c1..7b17ad9 100644 --- a/packages/oms-wallet-wagmi-connector/README.md +++ b/packages/oms-wallet-wagmi-connector/README.md @@ -12,7 +12,6 @@ import { omsWalletConnector } from '@0xsequence/oms-wallet-wagmi-connector' const oms = new OMSClient({ publishableKey: import.meta.env.VITE_OMS_PUBLISHABLE_KEY, - projectId: import.meta.env.VITE_OMS_PROJECT_ID, }) export const wagmiConfig = createConfig({ From 4225df52ee1369b41ea608de395db4c8a0793cf3 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Tue, 23 Jun 2026 13:55:53 +0300 Subject: [PATCH 7/9] Simplify Claude review workflow Remove the general Claude workflow, pin PR review to Opus 4.8, add manual review comments, and drop stale test secret wiring. --- .github/workflows/claude-code-review.yml | 18 +++++++++++++-- .github/workflows/claude.yml | 29 ------------------------ .github/workflows/tests.yml | 2 -- TESTING.md | 2 -- 4 files changed, 16 insertions(+), 35 deletions(-) delete mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index d9427cd..a785697 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -2,15 +2,28 @@ name: Claude Code Review on: pull_request: - types: [opened, synchronize] + types: [opened] + issue_comment: + types: [created] jobs: claude-review: - if: github.event.pull_request.user.login != 'dependabot[bot]' + if: | + ( + github.event_name == 'pull_request' && + github.event.pull_request.user.login != 'dependabot[bot]' + ) || + ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.issue.user.login != 'dependabot[bot]' && + contains(github.event.comment.body, '@claude review') + ) runs-on: ubuntu-latest permissions: contents: read pull-requests: write + issues: write id-token: write steps: - name: Checkout @@ -21,6 +34,7 @@ jobs: - uses: anthropics/claude-code-action@beta with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_GITHUB_ACTIONS }} + model: "claude-opus-4-8" direct_prompt: | Review this pull request. Focus on: - Correctness of SDK behavior changes (wallet auth, signing, transactions, indexer) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index 6d03ec8..0000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Claude - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && contains(github.event.issue.body, '@claude')) - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - issues: write - id-token: write - steps: - - uses: anthropics/claude-code-action@beta - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_GITHUB_ACTIONS }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bdd7ffc..a56b101 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,8 +36,6 @@ jobs: run: pnpm exec tsc --noEmit - name: Run tests - env: - OMS_PROJECT_ACCESS_KEY: ${{ secrets.OMS_PROJECT_ACCESS_KEY }} run: pnpm test - name: Build SDK diff --git a/TESTING.md b/TESTING.md index 8aabe83..9665a0c 100644 --- a/TESTING.md +++ b/TESTING.md @@ -48,8 +48,6 @@ How testing works in this repo. `AGENTS.md` points here so agents know how to ve - Bug fixes should include a regression test that would have caught the bug - Do not add tests that assert private implementation — test the externally visible promise - Network boundaries are stubbed; don't require live secrets for `pnpm test` -- Integration tests that need `OMS_PROJECT_ACCESS_KEY` are gated behind the CI secret; they - may be skipped locally when the env var is absent ### Public error contract tests From a354e11c777d73acb6bd9ae213a948acec5ed2d9 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Tue, 23 Jun 2026 17:26:30 +0300 Subject: [PATCH 8/9] Derive service URLs from publishable keys --- src/index.ts | 5 +- src/omsClient.ts | 50 +++++++++--------- src/omsEnvironment.ts | 50 +++++++++++++----- src/publishableKey.ts | 45 ++++++++++++++++ tests/errorContracts.test.ts | 6 +-- tests/networks.test.ts | 2 +- tests/oidcRedirectAuth.test.ts | 18 +++---- tests/omsClient.test.ts | 91 +++++++++++++++++++++++++++++++++ tests/walletSession.test.ts | 18 +++---- type-tests/oidcProviderTypes.ts | 59 ++++++++++++--------- 10 files changed, 253 insertions(+), 91 deletions(-) create mode 100644 src/publishableKey.ts create mode 100644 tests/omsClient.test.ts diff --git a/src/index.ts b/src/index.ts index 8918acf..41c1321 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,9 @@ export { OMSClient } from './omsClient.js' export { - defineOmsEnvironment, - defaultOmsEnvironment, + defaultOmsAuthConfig, + defineOmsAuthConfig, type OidcProviderConfig, type OmsAuthConfig, - type OmsEnvironment, } from './omsEnvironment.js' export { googleOidcProvider, diff --git a/src/omsClient.ts b/src/omsClient.ts index dad4943..c6c2b0e 100644 --- a/src/omsClient.ts +++ b/src/omsClient.ts @@ -1,36 +1,48 @@ import { WalletClient } from "./clients/walletClient.js"; -import {defaultOmsEnvironment, OmsEnvironment} from "./omsEnvironment.js"; +import { + DefaultOmsEnvironment, + omsEnvironmentFromPublishableKey, + OmsAuthConfig, + 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"; -import {OmsValidationError} from "./errors.js"; +import {parsePublishableKey} from "./publishableKey.js"; interface OMSClientBaseParams { publishableKey: string; + auth?: OmsAuthConfig; + environment?: never; storage?: StorageManager; redirectAuthStorage?: StorageManager; credentialSigner?: CredentialSigner; } -export type OMSClientParams = - OMSClientBaseParams & {environment: Env}; +export type OMSClientParams = + OMSClientBaseParams & (Auth extends OmsAuthConfig ? {auth: Auth} : {auth?: undefined}); -type DefaultOMSClientParams = OMSClientBaseParams & {environment?: undefined}; +type ProvidersFromAuth = + Auth extends {oidcProviders?: infer OidcProviders} + ? NonNullable extends Record + ? NonNullable + : never + : never; -class OMSClientImpl { +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; + constructor(params: OMSClientBaseParams) { + const parsedKey = parsePublishableKey(params.publishableKey); + const environment = omsEnvironmentFromPublishableKey(params.publishableKey, params.auth) as Env; const storage = params.storage ?? createDefaultStorage() - const projectId = projectIdFromPublishableKey(params.publishableKey) this.wallet = new WalletClient({ publishableKey: params.publishableKey, - projectId, + projectId: parsedKey.projectId, environment, storage, redirectAuthStorage: params.redirectAuthStorage, @@ -44,22 +56,12 @@ class OMSClientImpl { } } -export type OMSClient = OMSClientImpl; +export type OMSClient = OMSClientImpl; interface OMSClientConstructor { - new(params: DefaultOMSClientParams): OMSClient; - new(params: OMSClientParams): OMSClient; - new(params: OMSClientBaseParams & {environment?: OmsEnvironment}): OMSClient; + new(params: OMSClientParams): OMSClient; + new(params: OMSClientParams): OMSClient>>; + new(params: OMSClientBaseParams): OMSClient; } export const OMSClient: OMSClientConstructor = OMSClientImpl as unknown as OMSClientConstructor; - -function projectIdFromPublishableKey(publishableKey: string): string { - const match = /^pk_[^_]+_[^_]+_([^_]+)_[^_]+$/.exec(publishableKey); - if (!match) { - throw new OmsValidationError({ - message: "Invalid publishableKey.", - }); - } - return `prj_${match[1]}`; -} diff --git a/src/omsEnvironment.ts b/src/omsEnvironment.ts index 207636b..4080ca6 100644 --- a/src/omsEnvironment.ts +++ b/src/omsEnvironment.ts @@ -1,4 +1,5 @@ import {googleOidcProvider} from "./oidc.js"; +import {parsePublishableKey} from "./publishableKey.js"; export interface OidcProviderConfig { clientId: string; @@ -23,16 +24,41 @@ export interface OmsEnvironment< auth?: OmsAuthConfig; } -export const defaultOmsEnvironment = { - walletApiUrl: "https://sandbox-api.dev.polygon-dev.technology", - indexerGatewayUrl: "https://api.dev.polygon-dev.technology/v1/IndexerGateway/", - auth: { - oidcProviders: { - google: googleOidcProvider(), - }, - }, -} satisfies OmsEnvironment; - -export function defineOmsEnvironment(environment: Env): Env { - return environment; +const defaultOidcProviders = { + google: googleOidcProvider(), +}; + +export const defaultOmsAuthConfig = { + oidcProviders: defaultOidcProviders, +} satisfies OmsAuthConfig; + +export type DefaultOmsEnvironment = OmsEnvironment; + +type OidcProvidersFromAuth = + Auth extends {oidcProviders?: infer OidcProviders} + ? NonNullable extends Record + ? NonNullable + : never + : never; + +type ProvidersFromAuth = + Auth extends OmsAuthConfig + ? OidcProvidersFromAuth + : typeof defaultOidcProviders; + +export function defineOmsAuthConfig(auth: Auth): Auth { + return auth; +} + +export function omsEnvironmentFromPublishableKey( + publishableKey: string, + auth?: Auth, +): OmsEnvironment> { + const parsedKey = parsePublishableKey(publishableKey); + + return { + walletApiUrl: parsedKey.walletApiUrl, + indexerGatewayUrl: parsedKey.indexerGatewayUrl, + auth: auth ?? defaultOmsAuthConfig, + } as OmsEnvironment>; } diff --git a/src/publishableKey.ts b/src/publishableKey.ts new file mode 100644 index 0000000..58f5a9e --- /dev/null +++ b/src/publishableKey.ts @@ -0,0 +1,45 @@ +import {OmsValidationError} from "./errors.js"; + +interface PublishableKeyRoute { + prefix: string; + apiUrl: string; +} + +const publishableKeyRoutes: PublishableKeyRoute[] = [ + {prefix: "pk_dev_sdbx_", apiUrl: "https://sandbox-api.dev.polygon-dev.technology"}, + {prefix: "pk_dev_live_", apiUrl: "https://api.dev.polygon-dev.technology"}, + {prefix: "pk_stg_sdbx_", apiUrl: "https://sandbox-api.stg.polygon-dev.technology"}, + {prefix: "pk_stg_live_", apiUrl: "https://api.stg.polygon-dev.technology"}, + {prefix: "pk_sdbx_", apiUrl: "https://sandbox-api.polygon.technology"}, + {prefix: "pk_live_", apiUrl: "https://api.polygon.technology"}, +]; + +export interface ParsedPublishableKey { + projectId: string; + walletApiUrl: string; + indexerGatewayUrl: string; +} + +export function parsePublishableKey(publishableKey: string): ParsedPublishableKey { + const route = publishableKeyRoutes.find(({prefix}) => publishableKey.startsWith(prefix)); + if (!route) { + throw invalidPublishableKey(); + } + + const keyParts = publishableKey.slice(route.prefix.length).split("_"); + if (keyParts.length !== 2 || keyParts.some(part => part.length === 0)) { + throw invalidPublishableKey(); + } + + return { + projectId: `prj_${keyParts[0]}`, + walletApiUrl: route.apiUrl, + indexerGatewayUrl: `${route.apiUrl}/v1/IndexerGateway/`, + }; +} + +function invalidPublishableKey(): OmsValidationError { + return new OmsValidationError({ + message: "Invalid publishableKey.", + }); +} diff --git a/tests/errorContracts.test.ts b/tests/errorContracts.test.ts index cafb29c..8f8db8b 100644 --- a/tests/errorContracts.test.ts +++ b/tests/errorContracts.test.ts @@ -1572,13 +1572,9 @@ function createOmsClient(params: { redirectAuthStorage?: MemoryStorageManager | null } = {}): OMSClient { const clientParams: ConstructorParameters[0] = { - publishableKey: "pk_test_sdbx_project_key", + publishableKey: "pk_dev_sdbx_project_key", storage: new MemoryStorageManager(), credentialSigner: params.credentialSigner ?? new MockSigner(), - environment: { - walletApiUrl: "https://wallet.example", - indexerGatewayUrl: "https://indexer.example", - }, }; if (params.redirectAuthStorage !== null) { diff --git a/tests/networks.test.ts b/tests/networks.test.ts index 6dd7044..738cb81 100644 --- a/tests/networks.test.ts +++ b/tests/networks.test.ts @@ -50,7 +50,7 @@ describe("Networks", () => { it("is available from OMSClient", () => { const oms = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", + publishableKey: "pk_dev_sdbx_project_key", }); expect(oms.supportedNetworks).toBe(supportedNetworks); diff --git a/tests/oidcRedirectAuth.test.ts b/tests/oidcRedirectAuth.test.ts index 99376f0..9bc6561 100644 --- a/tests/oidcRedirectAuth.test.ts +++ b/tests/oidcRedirectAuth.test.ts @@ -2,7 +2,7 @@ import {afterEach, describe, expect, it, vi} from "vitest"; import {WalletClient} from "../src/clients/walletClient"; import type {CredentialSigner} from "../src/credentialSigner"; -import {defineOmsEnvironment, type OidcProviderConfig, type OmsEnvironment} from "../src/omsEnvironment"; +import {type OidcProviderConfig, type OmsEnvironment} from "../src/omsEnvironment"; import {googleOidcProvider} from "../src/oidc"; import {MemoryStorageManager} from "../src/storageManager"; import {WalletType} from "../src/generated/waas.gen"; @@ -174,7 +174,7 @@ describe("WalletClient OIDC redirect auth", () => { const wallet = createWalletClient({ redirectAuthStorage: new MemoryStorageManager(), - environment: defineOmsEnvironment({ + environment: { walletApiUrl: "https://wallet.example", indexerGatewayUrl: "https://indexer.example", auth: { @@ -186,7 +186,7 @@ describe("WalletClient OIDC redirect auth", () => { }, }, }, - }), + }, }); const result = await wallet.startOidcRedirectAuth({ @@ -246,7 +246,7 @@ describe("WalletClient OIDC redirect auth", () => { redirectAuthStorage: new MemoryStorageManager(), credentialSigner: signer, projectId: "proj_custom", - environment: defineOmsEnvironment({ + environment: { walletApiUrl: "https://wallet.example", indexerGatewayUrl: "https://indexer.example", auth: { @@ -257,7 +257,7 @@ describe("WalletClient OIDC redirect auth", () => { }), }, }, - }), + }, }); const result = await wallet.startOidcRedirectAuth({ @@ -327,7 +327,7 @@ describe("WalletClient OIDC redirect auth", () => { }))); const wallet = createWalletClient({ redirectAuthStorage: new MemoryStorageManager(), - environment: defineOmsEnvironment({ + environment: { walletApiUrl: "https://wallet.example", indexerGatewayUrl: "https://indexer.example", auth: { @@ -343,7 +343,7 @@ describe("WalletClient OIDC redirect auth", () => { }, }, }, - }), + }, }); const result = await wallet.startOidcRedirectAuth({ @@ -819,7 +819,7 @@ function createWalletClient { + vi.unstubAllGlobals(); +}); + +describe("OMSClient publishable key routing", () => { + it.each([ + ["pk_dev_sdbx_project_key", "https://sandbox-api.dev.polygon-dev.technology"], + ["pk_dev_live_project_key", "https://api.dev.polygon-dev.technology"], + ["pk_stg_sdbx_project_key", "https://sandbox-api.stg.polygon-dev.technology"], + ["pk_stg_live_project_key", "https://api.stg.polygon-dev.technology"], + ["pk_sdbx_project_key", "https://sandbox-api.polygon.technology"], + ["pk_live_project_key", "https://api.polygon.technology"], + ])("derives service URLs from %s", (publishableKey, apiUrl) => { + expect(parsePublishableKey(publishableKey)).toEqual({ + projectId: "prj_project", + walletApiUrl: apiUrl, + indexerGatewayUrl: `${apiUrl}/v1/IndexerGateway/`, + }); + }); + + it("uses the derived URLs for WaaS and IndexerGateway requests", async () => { + const fetchMock = vi.fn(async (input: RequestInfo | URL) => { + const url = input.toString(); + + if (url.endsWith("/v1/WaasPublic/IsValidMessageSignature")) { + return jsonResponse({isValid: true}); + } + + if (url.endsWith("/v1/IndexerGateway/GetTokenBalancesDetails")) { + return jsonResponse({ + page: {page: 0, pageSize: 40, more: false}, + nativeBalances: [], + balances: [], + }); + } + + throw new Error(`Unexpected request: ${url}`); + }); + vi.stubGlobal("fetch", fetchMock); + + const oms = new OMSClient({ + publishableKey: "pk_stg_live_project_key", + storage: new MemoryStorageManager(), + }); + + await expect(oms.wallet.isValidMessageSignature({ + network: Networks.polygon, + walletAddress, + message: "hello", + signature: "0xsignature", + })).resolves.toBe(true); + await expect(oms.indexer.getBalances({ + networks: [Networks.polygon], + walletAddress, + includeMetadata: false, + })).resolves.toMatchObject({ + status: 200, + nativeBalances: [], + balances: [], + }); + + expect(fetchMock.mock.calls[0][0].toString()).toBe( + "https://api.stg.polygon-dev.technology/v1/WaasPublic/IsValidMessageSignature", + ); + expect(fetchMock.mock.calls[1][0].toString()).toBe( + "https://api.stg.polygon-dev.technology/v1/IndexerGateway/GetTokenBalancesDetails", + ); + }); + + it("rejects unsupported publishable key prefixes", () => { + expect(() => new OMSClient({ + publishableKey: "pk_test_sdbx_project_key", + storage: new MemoryStorageManager(), + })).toThrow("Invalid publishableKey."); + }); +}); + +function jsonResponse(body: unknown): Response { + return new Response(JSON.stringify(body), { + status: 200, + headers: {"Content-Type": "application/json"}, + }); +} diff --git a/tests/walletSession.test.ts b/tests/walletSession.test.ts index 4d2a78b..414d704 100644 --- a/tests/walletSession.test.ts +++ b/tests/walletSession.test.ts @@ -51,8 +51,7 @@ describe("WalletClient session storage", () => { vi.stubGlobal("localStorage", undefined); const client = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", - environment: testEnvironment(), + publishableKey: "pk_dev_sdbx_project_key", credentialSigner: new MockSigner(), }); @@ -197,8 +196,7 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", - environment: testEnvironment(), + publishableKey: "pk_dev_sdbx_project_key", storage, credentialSigner: signer, }); @@ -232,8 +230,7 @@ describe("WalletClient session storage", () => { const nextOnSessionExpired = vi.fn(); const nextClient = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", - environment: testEnvironment(), + publishableKey: "pk_dev_sdbx_project_key", storage, credentialSigner: signer, }); @@ -265,8 +262,7 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", - environment: testEnvironment(), + publishableKey: "pk_dev_sdbx_project_key", storage, credentialSigner: signer, }); @@ -292,8 +288,7 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", - environment: testEnvironment(), + publishableKey: "pk_dev_sdbx_project_key", storage, credentialSigner: signer, }); @@ -605,8 +600,7 @@ describe("WalletClient session storage", () => { storage.set(Constants.sessionEmailStorageKey, "user@example.com"); const client = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", - environment: testEnvironment(), + publishableKey: "pk_dev_sdbx_project_key", storage, credentialSigner: new MockSigner(), }); diff --git a/type-tests/oidcProviderTypes.ts b/type-tests/oidcProviderTypes.ts index 30a4b2d..dd63b95 100644 --- a/type-tests/oidcProviderTypes.ts +++ b/type-tests/oidcProviderTypes.ts @@ -2,6 +2,7 @@ import {WalletClient, type OidcProviderName} from "../src/clients/walletClient"; import { Networks, OMSClient, + defineOmsAuthConfig, findNetworkById, findNetworkByName, supportedNetworks, @@ -17,24 +18,22 @@ import { type GetBalancesParams, type GetTransactionHistoryParams, type IndexerNetworkType, + type OmsAuthConfig, type TokenBalance, type TokenBalancesPage, type TokenContractInfo, type TokenMetadata, type TransactionHistoryResult, } from "../src/index"; -import {defineOmsEnvironment, type OmsEnvironment} from "../src/omsEnvironment"; +import {omsEnvironmentFromPublishableKey} from "../src/omsEnvironment"; import {googleOidcProvider} from "../src/oidc"; -const environment = defineOmsEnvironment({ - walletApiUrl: "https://wallet.example", - indexerGatewayUrl: "https://indexer.example", - auth: { - oidcProviders: { - google: googleOidcProvider(), - }, +const auth = defineOmsAuthConfig({ + oidcProviders: { + google: googleOidcProvider(), }, }); +const environment = omsEnvironmentFromPublishableKey("pk_dev_sdbx_project_key", auth); type ProviderName = OidcProviderName; @@ -74,20 +73,20 @@ if (false) { } const defaultClient = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", + publishableKey: "pk_dev_sdbx_project_key", }); // @ts-expect-error publishableKey is required. new OMSClient({}); // @ts-expect-error projectId is not a constructor parameter. -new OMSClient({publishableKey: "pk_test_sdbx_project_key", projectId: "project-id"}); +new OMSClient({publishableKey: "pk_dev_sdbx_project_key", projectId: "project-id"}); // @ts-expect-error projectAccessKey initializer name is not supported. -new OMSClient({projectAccessKey: "pk_test_sdbx_project_key"}); +new OMSClient({projectAccessKey: "pk_dev_sdbx_project_key"}); // @ts-expect-error publicApiKey initializer name is not supported. -new OMSClient({publicApiKey: "pk_test_sdbx_project_key"}); +new OMSClient({publicApiKey: "pk_dev_sdbx_project_key"}); // @ts-expect-error authorizationScope initializer name is not supported. -new OMSClient({publishableKey: "pk_test_sdbx_project_key", authorizationScope: "project-id"}); +new OMSClient({publishableKey: "pk_dev_sdbx_project_key", authorizationScope: "project-id"}); new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", + publishableKey: "pk_dev_sdbx_project_key", // @ts-expect-error session expiry is subscribed through wallet.onSessionExpired, not constructor params. onSessionExpired: () => {}, }); @@ -195,39 +194,49 @@ void defaultClient.wallet.startOidcRedirectAuth({ redirectUri: "https://app.example/auth/callback", }); void defaultClient.wallet.startOidcRedirectAuth({ - // @ts-expect-error github is not configured on the default environment. + // @ts-expect-error github is not configured on the default auth config. provider: "github", redirectUri: "https://app.example/auth/callback", }); -const customEnvironmentWithoutProviders = defineOmsEnvironment({ - walletApiUrl: "https://wallet.example", - indexerGatewayUrl: "https://indexer.example", -}); const customClient = new OMSClient({ - publishableKey: "pk_test_sdbx_project_key", - environment: customEnvironmentWithoutProviders, + publishableKey: "pk_dev_sdbx_project_key", + auth, }); let broadlyTypedClient: OMSClient; broadlyTypedClient = customClient; void broadlyTypedClient; void customClient.wallet.startOidcRedirectAuth({ + provider: "google", + redirectUri: "https://app.example/auth/callback", +}); + +const noProviderClient = new OMSClient({ + publishableKey: "pk_dev_sdbx_project_key", + auth: {}, +}); +void noProviderClient.wallet.startOidcRedirectAuth({ // @ts-expect-error string provider names are not available without configured providers. provider: "google", redirectUri: "https://app.example/auth/callback", }); +new OMSClient({ + publishableKey: "pk_dev_sdbx_project_key", + // @ts-expect-error environment URL overrides are not constructor parameters. + environment, +}); function createClient(params: { publishableKey: string; - environment?: OmsEnvironment; + auth?: OmsAuthConfig; }) { return new OMSClient(params); } void createClient({ - publishableKey: "pk_test_sdbx_project_key", + publishableKey: "pk_dev_sdbx_project_key", }); void createClient({ - publishableKey: "pk_test_sdbx_project_key", - environment: customEnvironmentWithoutProviders, + publishableKey: "pk_dev_sdbx_project_key", + auth, }); From 97288fc77d365711f211010d1f34bad2df59fcf4 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Tue, 23 Jun 2026 17:26:39 +0300 Subject: [PATCH 9/9] Document publishable key routing --- API.md | 55 ++++++++++++++++++++++++++++++------------------------- README.md | 34 +++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/API.md b/API.md index 190aec0..1fefd1a 100644 --- a/API.md +++ b/API.md @@ -38,7 +38,7 @@ - [Errors](#errors) - [Types](#types) - [Network](#network) - - [OmsEnvironment](#omsenvironment) + - [OmsAuthConfig](#omsauthconfig) - [OidcProviderConfig](#oidcproviderconfig) - [StorageManager](#storagemanager) - [CredentialSigner](#credentialsigner) @@ -84,7 +84,7 @@ const oms = new OMSClient({ ```typescript new OMSClient(params: { publishableKey: string - environment?: OmsEnvironment + auth?: OmsAuthConfig storage?: StorageManager redirectAuthStorage?: StorageManager credentialSigner?: CredentialSigner @@ -95,12 +95,23 @@ new OMSClient(params: { | Name | Type | Required | Description | |---|---|---|---| -| `publishableKey` | `string` | Yes | Your OMS publishable key. Wallet and IndexerGateway requests send this as the `Api-Key` header. | -| `environment` | `OmsEnvironment` | No | API endpoint configuration. Defaults to the SDK's configured OMS endpoints. | +| `publishableKey` | `string` | Yes | Your OMS publishable key. Wallet and IndexerGateway requests send this as the `Api-Key` header and derive service endpoints from its prefix. | +| `auth` | `OmsAuthConfig` | No | OIDC provider configuration. Defaults to the built-in Google provider. | | `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. | | `credentialSigner` | `CredentialSigner` | No | Request credential signer. Defaults to a non-extractable WebCrypto P-256 signer (`ecdsa-p256-sha256`) where WebCrypto is available. | +`OMSClient` derives the WaaS API base URL from the publishable key prefix. IndexerGateway uses the same base URL with `/v1/IndexerGateway/`. + +| Publishable key prefix | API base URL | +|---|---| +| `pk_dev_sdbx_` | `https://sandbox-api.dev.polygon-dev.technology` | +| `pk_dev_live_` | `https://api.dev.polygon-dev.technology` | +| `pk_stg_sdbx_` | `https://sandbox-api.stg.polygon-dev.technology` | +| `pk_stg_live_` | `https://api.stg.polygon-dev.technology` | +| `pk_sdbx_` | `https://sandbox-api.polygon.technology` | +| `pk_live_` | `https://api.polygon.technology` | + **Properties** | Name | Type | Description | @@ -266,7 +277,7 @@ startOidcRedirectAuth(params: { Starts an OIDC authorization-code PKCE flow and returns the provider authorization URL. If a wallet session is already active, it is cleared before the new auth attempt starts. The pending verifier/state is stored in transient redirect auth storage so the callback can complete after a full-page redirect. -If `provider` is a string, it must match a configured `environment.auth.oidcProviders` key. Passing an `OidcProviderConfig` object directly is also supported. +If `provider` is a string, it must match a configured `auth.oidcProviders` key. Passing an `OidcProviderConfig` object directly is also supported. In direct mode, `redirect_uri` is `redirectUri`. In relay mode, `redirect_uri` is `relayRedirectUri`, and the encoded state includes the final app `redirect_uri`. @@ -884,39 +895,33 @@ findNetworkByName(name: string): Network | undefined | `Networks.avalancheTestnet` | 43113 | `avalanche-testnet` | `Avalanche Testnet` | `AVAX` | `https://subnets-test.avax.network/c-chain` | | `Networks.katana` | 747474 | `katana` | `Katana` | `ETH` | `https://katanascan.com` | -### OmsEnvironment +### OmsAuthConfig ```typescript -interface OmsEnvironment { - walletApiUrl: string - indexerGatewayUrl: string - auth?: { - oidcProviders?: Record - } +interface OmsAuthConfig { + oidcProviders?: Record } ``` | Field | Type | Description | |---|---|---| -| `walletApiUrl` | `string` | Base URL of the WaaS Wallet RPC host. | -| `indexerGatewayUrl` | `string` | Base URL of the IndexerGateway host, e.g. `"https://api.example.com/v1/IndexerGateway/"`. | -| `auth.oidcProviders` | `Record` | OIDC provider configurations addressable by provider key. | +| `oidcProviders` | `Record` | OIDC provider configurations addressable by provider key. | -The default is exported as `defaultOmsEnvironment`, uses `https://sandbox-api.dev.polygon-dev.technology` as the WaaS API base URL, and includes the `google` OIDC provider. +When `auth` is omitted, the SDK configures the built-in `google` provider. Passing `auth` replaces the configured provider set. -Use `defineOmsEnvironment` to preserve typed custom OIDC provider keys: +Use `defineOmsAuthConfig` to preserve typed custom OIDC provider keys: ```typescript -const environment = defineOmsEnvironment({ - ...defaultOmsEnvironment, - auth: { - ...defaultOmsEnvironment.auth, - oidcProviders: { - ...defaultOmsEnvironment.auth?.oidcProviders, - custom: customOidcProvider, - }, +const auth = defineOmsAuthConfig({ + oidcProviders: { + custom: customOidcProvider, }, }) + +const oms = new OMSClient({ + publishableKey: 'your-publishable-key', + auth, +}) ``` --- diff --git a/README.md b/README.md index a9fcdb1..c9149a4 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ const oms = new OMSClient({ }) ``` -By default, wallet requests use the sandbox WaaS API at `https://sandbox-api.dev.polygon-dev.technology`. +The SDK derives the WaaS API and IndexerGateway endpoints from the publishable key prefix. -In Vite browser apps, keep that value in local environment variables: +In Vite browser apps, keep the publishable key in local environment variables: ```typescript function requiredEnv(name: string, value: string | undefined): string { @@ -180,7 +180,7 @@ The returned pending selection is bound to the verified auth flow and signer. Ho ### 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. +Google redirect auth is configured by default. The redirect auth APIs are provider-neutral, so `auth.oidcProviders` can replace the configured provider set. ```typescript const oms = new OMSClient({ @@ -413,20 +413,36 @@ const tx = await oms.wallet.sendTransaction({ ## Configuration -### Custom Environment +### Publishable-Key Routing + +`OMSClient` derives service endpoints from the publishable key. WaaS requests use the API base URL directly; indexer requests use the same base URL with `/v1/IndexerGateway/`. + +| Publishable key prefix | API base URL | +|---|---| +| `pk_dev_sdbx_` | `https://sandbox-api.dev.polygon-dev.technology` | +| `pk_dev_live_` | `https://api.dev.polygon-dev.technology` | +| `pk_stg_sdbx_` | `https://sandbox-api.stg.polygon-dev.technology` | +| `pk_stg_live_` | `https://api.stg.polygon-dev.technology` | +| `pk_sdbx_` | `https://sandbox-api.polygon.technology` | +| `pk_live_` | `https://api.polygon.technology` | + +### Custom OIDC Providers ```typescript const oms = new OMSClient({ publishableKey: 'your-publishable-key', - environment: { - walletApiUrl: 'https://staging-wallet.example.com', - indexerGatewayUrl: 'https://staging-indexer.example.com/v1/IndexerGateway/', + auth: { + oidcProviders: { + custom: { + clientId: 'custom-client-id', + issuer: 'https://issuer.example', + authorizationUrl: 'https://issuer.example/oauth/authorize', + }, + }, }, }) ``` -For indexer requests, `indexerGatewayUrl` points at the IndexerGateway base URL. - ### 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: