@tesser-payments/sdk-ts 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,7 +24,6 @@ const signer = new LocalSigner({
24
24
  privateKey: process.env.SIGNING_PRIVATE_KEY!,
25
25
  enclaveId: process.env.SIGNING_ENCLAVE_ID!,
26
26
  },
27
- apiBaseUrl: 'https://api.tesser.xyz', // sandbox: https://api-sandbox.tesser.xyz
28
27
  });
29
28
 
30
29
  const { signature } = await signer.signCreateWallet({
@@ -50,8 +49,8 @@ const res = await fetch(`${API_BASE_URL}/v1/accounts/wallets`, {
50
49
  `signature` is `base64(JSON.stringify({ body, stamp }))` — drop it into the
51
50
  Tesser API request body as the documented `signature` field.
52
51
 
53
- `LocalSigner` requires the Tesser API base URL (`https://api.tesser.xyz` or
54
- `https://api-sandbox.tesser.xyz`); a trailing `/` or `/v1` is removed. Pass an
52
+ `apiBaseUrl` is optional; pass it to use only that URL (`https://api.tesser.xyz`
53
+ or `https://api-sandbox.tesser.xyz`; a trailing `/` or `/v1` is removed). Pass an
55
54
  optional `fetch` implementation to configure a proxy.
56
55
 
57
56
  `signCreateWallet` signs a wallet-creation request. For `stablecoin_ethereum`,
@@ -59,6 +58,13 @@ it first fetches the request to sign from the Tesser API and checks that it
59
58
  matches your parameters. The private key never leaves your process. Tesser
60
59
  currently supports managed wallets for `stablecoin_ethereum` only.
61
60
 
61
+ `LocalSigner({ signing })` works with production and sandbox signing keys.
62
+ Without `apiBaseUrl`, the first `stablecoin_ethereum` `signCreateWallet` tries
63
+ production and, if production does not accept your signing key, sandbox, and
64
+ remembers it for later calls. Set `apiBaseUrl` if your network allows only one
65
+ Tesser host, or if you create a new signer per request (each new signer detects
66
+ again). Send your own API requests to the environment of your signing key.
67
+
62
68
  **Private-key handling.** `signer.signing` exposes only `{ publicKey, enclaveId }`.
63
69
  The private key lives on the instance as an ECMA private field and is not
64
70
  reachable via property access, `console.log`, or `JSON.stringify`. The SDK still
@@ -141,7 +147,8 @@ the request `signCreateWallet` makes to Tesser for `stablecoin_ethereum`.
141
147
  `errors[]` (with `errorCode` for the first one). `TesserConnectionError`,
142
148
  `TesserTimeoutError`, and a `TesserAPIError` with status 429 or 5xx are safe to
143
149
  retry; a `TesserAPIError` with any other 4xx status fails the same way on
144
- retry.
150
+ retry. Without `apiBaseUrl`, if neither production nor sandbox accepts your
151
+ signing key, the `TesserAPIError` from production is thrown.
145
152
 
146
153
  Each error carries the original failure as `.cause` and can be discriminated via
147
154
  `instanceof` or the static `.is()` guard:
package/dist/index.js CHANGED
@@ -21473,7 +21473,7 @@ async function stamp(keys, body) {
21473
21473
  }
21474
21474
 
21475
21475
  // src/internal/version.ts
21476
- var SDK_VERSION = "0.1.0";
21476
+ var SDK_VERSION = "0.2.0";
21477
21477
 
21478
21478
  // src/signing/unsigned-payload.ts
21479
21479
  var UNSIGNED_PAYLOAD_PATH = "/v1/accounts/wallets/unsigned-payload";
@@ -21482,12 +21482,27 @@ var ETHEREUM_ADDRESS_FORMAT = "ADDRESS_FORMAT_ETHEREUM";
21482
21482
  var UNSIGNED_PAYLOAD_TIMEOUT_MS = 1e4;
21483
21483
  var LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
21484
21484
  var MAX_FAILURE_CHAIN_DEPTH = 10;
21485
- function createTesserApiConnection(options) {
21485
+ var PRODUCTION_API_BASE_URL = "https://api.tesser.xyz";
21486
+ var SANDBOX_API_BASE_URL = "https://api-sandbox.tesser.xyz";
21487
+ var DEFAULT_API_BASE_URLS = [PRODUCTION_API_BASE_URL, SANDBOX_API_BASE_URL];
21488
+ var STAMP_REJECTED_ERROR_CODE = "signing-4010";
21489
+ function createTesserApiConnection({
21490
+ apiBaseUrl,
21491
+ fetch: injectedFetch,
21492
+ defaultBaseUrls = DEFAULT_API_BASE_URLS
21493
+ }) {
21486
21494
  return {
21487
- baseUrl: normalizeApiBaseUrl(options.apiBaseUrl),
21488
- fetch: resolveFetchImplementation(options.fetch)
21495
+ fetch: resolveFetchImplementation(injectedFetch),
21496
+ defaultBaseUrls,
21497
+ resolvedBaseUrl: resolveExplicitApiBaseUrl(apiBaseUrl)
21489
21498
  };
21490
21499
  }
21500
+ function resolveExplicitApiBaseUrl(apiBaseUrl) {
21501
+ if (apiBaseUrl === undefined) {
21502
+ return;
21503
+ }
21504
+ return normalizeApiBaseUrl(apiBaseUrl);
21505
+ }
21491
21506
  function resolveFetchImplementation(injectedFetch) {
21492
21507
  if (injectedFetch !== undefined) {
21493
21508
  return injectedFetch;
@@ -21533,9 +21548,35 @@ function parseUrl(value) {
21533
21548
  }
21534
21549
  async function fetchCheckedCreateWalletAccountsBody(options) {
21535
21550
  const { api, signing, params } = options;
21551
+ if (api.resolvedBaseUrl !== undefined) {
21552
+ return fetchCheckedBodyFrom(api.resolvedBaseUrl, api.fetch, signing, params);
21553
+ }
21554
+ return detectEnvironmentAndFetchCheckedBody({ api, signing, params });
21555
+ }
21556
+ async function detectEnvironmentAndFetchCheckedBody(options) {
21557
+ const { api, signing, params } = options;
21558
+ let firstStampRejection;
21559
+ for (const baseUrl of api.defaultBaseUrls) {
21560
+ try {
21561
+ const body = await fetchCheckedBodyFrom(baseUrl, api.fetch, signing, params);
21562
+ api.resolvedBaseUrl = baseUrl;
21563
+ return body;
21564
+ } catch (error) {
21565
+ if (!isStampRejected(error)) {
21566
+ throw error;
21567
+ }
21568
+ firstStampRejection ??= error;
21569
+ }
21570
+ }
21571
+ throw firstStampRejection;
21572
+ }
21573
+ function isStampRejected(error) {
21574
+ return error instanceof TesserAPIError && error.status === 401 && error.errorCode === STAMP_REJECTED_ERROR_CODE;
21575
+ }
21576
+ async function fetchCheckedBodyFrom(baseUrl, fetchImplementation, signing, params) {
21536
21577
  const query = buildUnsignedPayloadQuery(signing, params);
21537
21578
  const stamped = await stamp({ publicKey: signing.publicKey, privateKey: signing.privateKey }, buildCanonicalUnsignedPayloadMessage(query));
21538
- const response = await sendUnsignedPayloadRequest(api, buildUnsignedPayloadUrl(api, query), stamped);
21579
+ const response = await sendUnsignedPayloadRequest(fetchImplementation, buildUnsignedPayloadUrl(baseUrl, query), stamped);
21539
21580
  const body = parseUnsignedPayloadBody(response);
21540
21581
  assertUnsignedPayloadMatchesRequest(body, signing, params);
21541
21582
  return body;
@@ -21556,7 +21597,7 @@ function buildCanonicalUnsignedPayloadMessage(query) {
21556
21597
  timestampMs: query.timestampMs
21557
21598
  });
21558
21599
  }
21559
- function buildUnsignedPayloadUrl(api, query) {
21600
+ function buildUnsignedPayloadUrl(baseUrl, query) {
21560
21601
  const queryEntries = [
21561
21602
  ["organizationId", query.organizationId],
21562
21603
  ["name", query.name],
@@ -21564,10 +21605,9 @@ function buildUnsignedPayloadUrl(api, query) {
21564
21605
  ["timestampMs", query.timestampMs]
21565
21606
  ];
21566
21607
  const queryString = queryEntries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
21567
- return `${api.baseUrl}${UNSIGNED_PAYLOAD_PATH}?${queryString}`;
21608
+ return `${baseUrl}${UNSIGNED_PAYLOAD_PATH}?${queryString}`;
21568
21609
  }
21569
- async function sendUnsignedPayloadRequest(api, url, stamped) {
21570
- const fetchImplementation = api.fetch;
21610
+ async function sendUnsignedPayloadRequest(fetchImplementation, url, stamped) {
21571
21611
  try {
21572
21612
  const response = await fetchImplementation(url, {
21573
21613
  method: "GET",
@@ -50,10 +50,12 @@ export interface SignedStepResult {
50
50
  export interface LocalSignerOptions {
51
51
  signing: SigningConfig;
52
52
  /**
53
- * Tesser API base URL: `https://api.tesser.xyz` in production,
54
- * `https://api-sandbox.tesser.xyz` in sandbox. A trailing `/` or `/v1` is removed.
53
+ * Optional Tesser API base URL. Leave it unset and the signer finds the environment
54
+ * your signing key belongs to, production or sandbox. When set, only this URL is used:
55
+ * `https://api.tesser.xyz` in production, `https://api-sandbox.tesser.xyz` in sandbox.
56
+ * A trailing `/` or `/v1` is removed.
55
57
  */
56
- apiBaseUrl: string;
58
+ apiBaseUrl?: string | undefined;
57
59
  /**
58
60
  * `fetch` implementation used for requests to Tesser. Inject one to configure a
59
61
  * proxy. Defaults to the global `fetch`.
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.1.0";
1
+ export declare const SDK_VERSION = "0.2.0";
package/dist/signer.d.ts CHANGED
@@ -12,15 +12,15 @@ export interface PublicSigningInfo {
12
12
  * Produces locally-signed activity payloads for Tesser API operations.
13
13
  *
14
14
  * Supports `signCreateWallet` and `signStep`. Construction validates that the
15
- * signing keys are non-blank and that `apiBaseUrl` is an https URL.
15
+ * signing keys are non-blank and that `apiBaseUrl`, if set, is an https URL.
16
16
  *
17
17
  * Key handling: the private key is stored in an ECMA private field (`#privateKey`)
18
18
  * and is not reachable via `signer.signing.privateKey` or any other public
19
19
  * surface. `signer.signing` exposes only `{ publicKey, enclaveId }`, so logging
20
20
  * `signer` or its `signing` property will not leak the private key.
21
21
  *
22
- * Thread-safety: stateless and reentrant. A single LocalSigner instance may
23
- * be shared across concurrent calls; the SDK never mutates internal state.
22
+ * Thread-safety: the only state is the environment remembered after the first
23
+ * Ethereum wallet request; a single LocalSigner may be shared across concurrent calls.
24
24
  */
25
25
  export declare class LocalSigner {
26
26
  #private;
@@ -39,7 +39,8 @@ export declare class LocalSigner {
39
39
  * Tesser does not match your parameters.
40
40
  * @throws {TesserAPIError} if Tesser answers with an error status or an
41
41
  * invalid response. Safe to retry for status 429 or 5xx; other 4xx statuses
42
- * fail the same way on retry.
42
+ * fail the same way on retry. Without `apiBaseUrl`, if neither production nor
43
+ * sandbox accepts your signing key, the error from production is thrown.
43
44
  * @throws {TesserConnectionError} if the request to Tesser fails at the
44
45
  * network level. Safe to retry.
45
46
  * @throws {TesserTimeoutError} if the request to Tesser times out. Safe to retry.
@@ -1,11 +1,13 @@
1
1
  import type { CreateWalletParams, SigningConfig } from '../internal/types.js';
2
2
  export interface TesserApiConnection {
3
- baseUrl: string;
4
3
  fetch: typeof fetch;
4
+ defaultBaseUrls: readonly string[];
5
+ resolvedBaseUrl: string | undefined;
5
6
  }
6
- export declare function createTesserApiConnection(options: {
7
+ export declare function createTesserApiConnection({ apiBaseUrl, fetch: injectedFetch, defaultBaseUrls, }: {
7
8
  apiBaseUrl: unknown;
8
9
  fetch: typeof fetch | undefined;
10
+ defaultBaseUrls?: readonly string[];
9
11
  }): TesserApiConnection;
10
12
  export declare function normalizeApiBaseUrl(apiBaseUrl: unknown): string;
11
13
  export declare function fetchCheckedCreateWalletAccountsBody(options: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tesser-payments/sdk-ts",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "TypeScript SDK for Tesser API: sign signCreateWallet and signStep requests locally. No OAuth — bring your own client.",
5
5
  "type": "module",
6
6
  "exports": {