@seams/wallet-server 0.53.0 → 0.54.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
@@ -4,17 +4,17 @@ This package owns the reusable signer server surface: route adapters,
4
4
  WebAuthn verification policy, threshold signing services, signer storage
5
5
  adapters, and server wasm bindings.
6
6
 
7
- The package version is `@seams/wallet-server@0.53.0`. Pair it with
8
- `@seams/wallet@0.53.0` and matching hosted assets. Use `@seams/wallet-cli@0.29.0`.
7
+ The package version is `@seams/wallet-server@0.54.0`. Pair it with
8
+ `@seams/wallet@0.54.0` and matching hosted assets. Use `@seams/wallet-cli@0.29.0`.
9
9
  The wallet iframe protocol is **23.0.0**.
10
10
 
11
11
  ```sh
12
- pnpm add @seams/wallet-server@0.53.0
12
+ pnpm add @seams/wallet-server@0.54.0
13
13
  ```
14
14
 
15
15
  ## Google BYO authentication
16
16
 
17
- Version 0.53.0 verifies a configured application's signed session assertion or the app's Google ID token or exchanges it for a browser-bound handoff.
17
+ Version 0.54.0 verifies a configured application's signed session assertion or the app's Google ID token or exchanges it for a browser-bound handoff.
18
18
  An environment-specific Google client configuration and an owner-approved wallet association are required.
19
19
  Apply the packaged signer migrations before enabling the feature.
20
20
  External sessions grant settings reads and remain separate from owner and signing credentials.
@@ -3,17 +3,17 @@
3
3
  "walletSecurityPolicy": "scoped_seals_and_owner_proofs_v1",
4
4
  "package": {
5
5
  "name": "@seams/wallet-server",
6
- "version": "0.53.0"
6
+ "version": "0.54.0"
7
7
  },
8
8
  "workers": {
9
9
  "router": {
10
10
  "entry": "cloudflare-router-ab/build/router/worker/shim.mjs",
11
- "fingerprint": "545a63bcf00b849d9c09549aa139c8cce7ec9e2b608251dd9b0a259a3af03dcc",
11
+ "fingerprint": "22e885129a9dbc5ad00599cc4dc3b4ce71a833fa3fa26993530200abda4a47d1",
12
12
  "files": [
13
13
  {
14
14
  "path": "cloudflare-router-ab/build/router/build-stamp.json",
15
15
  "bytes": 119,
16
- "sha256": "850505578f1b6a618d7da3e744505278b94a7b2d899c67ba95f295cdc00b9424"
16
+ "sha256": "8a75df14351fb43abefdd49c9660727594a5b751ad61d8916aabffe8492f78ff"
17
17
  },
18
18
  {
19
19
  "path": "cloudflare-router-ab/build/router/index.js",
@@ -39,12 +39,12 @@
39
39
  },
40
40
  "deriver-a": {
41
41
  "entry": "cloudflare-router-ab/build/deriver-a/worker/shim.mjs",
42
- "fingerprint": "c615c1214f9dccc5f8faef33d87ad50b9c2da58815d3c9ff1c0ded140c352b80",
42
+ "fingerprint": "bdd26687d013f61d6e6638fe29b0d457813fd4ee7bc731c6a9f2d4ba1b0faa28",
43
43
  "files": [
44
44
  {
45
45
  "path": "cloudflare-router-ab/build/deriver-a/build-stamp.json",
46
46
  "bytes": 122,
47
- "sha256": "141ea2882e6d4e711ae554df0405b667c7f2ce2d9dcf2e80f39be34827440f5d"
47
+ "sha256": "06905532466e1015afdd6c5ae77945cc5fd117548e10769f3c26027d82d94a0c"
48
48
  },
49
49
  {
50
50
  "path": "cloudflare-router-ab/build/deriver-a/index.js",
@@ -70,12 +70,12 @@
70
70
  },
71
71
  "deriver-b": {
72
72
  "entry": "cloudflare-router-ab/build/deriver-b/worker/shim.mjs",
73
- "fingerprint": "68478b8ffa6cad1ca6d87594ee4913c26d70e0b1a1fde9c17f690a289ff7a929",
73
+ "fingerprint": "d998751bf0efb8861236a8b8f6b0314dd8d41eaaca2f542497085e9d8b62e504",
74
74
  "files": [
75
75
  {
76
76
  "path": "cloudflare-router-ab/build/deriver-b/build-stamp.json",
77
77
  "bytes": 122,
78
- "sha256": "8a8c263d9c26677c1009d5d4280aab6c50ab514872355cc3a994f5d8a9a56e28"
78
+ "sha256": "fdec4e26b25e217da83b6f6c82c014d4689a95dbb47fd14333cd01f4f870e97b"
79
79
  },
80
80
  {
81
81
  "path": "cloudflare-router-ab/build/deriver-b/index.js",
@@ -101,12 +101,12 @@
101
101
  },
102
102
  "signing-worker": {
103
103
  "entry": "cloudflare-router-ab/build/signing-worker/worker/shim.mjs",
104
- "fingerprint": "65e15e1eaa5974a72bc1315d7fd5464c50839f81cf61694bc85ec803039c6057",
104
+ "fingerprint": "2d666492dbaa78826cedd663257c2eab86fe23ec5552a1b59b683717aebfbe3e",
105
105
  "files": [
106
106
  {
107
107
  "path": "cloudflare-router-ab/build/signing-worker/build-stamp.json",
108
108
  "bytes": 127,
109
- "sha256": "c093ee338224ddc793eae6dbec9e8fde4d6cbb0e94ad9b241b20cd070a08060f"
109
+ "sha256": "1ace62b6964ee4b8b9ac30bb49f315890e2742be54e14c1b8ee78237f3e808c3"
110
110
  },
111
111
  {
112
112
  "path": "cloudflare-router-ab/build/signing-worker/index.js",
@@ -132,12 +132,12 @@
132
132
  },
133
133
  "tenant-root-control-plane": {
134
134
  "entry": "cloudflare-router-ab/build/tenant-root-control-plane/worker/shim.mjs",
135
- "fingerprint": "9ad9ae1d8d3d969c8bc9c19566b30d9179c5a3dfb453fd493fa84622c6a7070e",
135
+ "fingerprint": "9fa94f9d652e8e36e68f318ff2a44be844ff398f6317d80467d0e0fa836d522f",
136
136
  "files": [
137
137
  {
138
138
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json",
139
139
  "bytes": 139,
140
- "sha256": "12e3bdfda49b0076134fb773b9300b3fad6e13653a470130967e3f30f818c92c"
140
+ "sha256": "2e7804f6e031ad965621682f581c0540069c2a25a182dd763c80d7f4d75dd98a"
141
141
  },
142
142
  {
143
143
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/index.js",
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-a-entrypoint","started_at_ms":1794064320525}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-a-entrypoint","started_at_ms":1794125422353}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-b-entrypoint","started_at_ms":1794064470181}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-b-entrypoint","started_at_ms":1794125619132}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-router-entrypoint","started_at_ms":1794064608782}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-router-entrypoint","started_at_ms":1794125797895}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-signing-worker-entrypoint","started_at_ms":1794064086560}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-signing-worker-entrypoint","started_at_ms":1794125116180}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":false,"features":"strict-worker-tenant-root-control-plane-entrypoint","started_at_ms":1794064771371}
1
+ {"profile":"release","wallet_objects":false,"features":"strict-worker-tenant-root-control-plane-entrypoint","started_at_ms":1794126001644}
@@ -44,6 +44,7 @@ import { handleRouterAbEd25519YaoRecoveryRequestScopedCloudflareV1 } from "./rou
44
44
  import { RouterAbEd25519YaoRecoveryWalletSessionAuthorizationAdapter } from "./router/domains/ed25519Yao/recovery/routerAbEd25519YaoRecoveryWalletSessionAuthorization.js";
45
45
  import "./cloud-host.js";
46
46
  import { WalletAuthorizationRelocationRoutes } from "./router/cloudflare/runtime/walletAuthorizationRelocationRoutes.js";
47
+ import { createSpeakToSignVerifyRouteExtensions, readSpeakToSignVerifyConfig } from "./router/domains/speakToSign/speakToSignVerify.js";
47
48
  //#region src/hosted-wallet-gateway.ts
48
49
  function readStagingHostedWalletOrigins(env) {
49
50
  return readEnvironmentCsv(requireEnvironmentString(env, "HOSTED_WALLET_ORIGINS"));
@@ -390,19 +391,27 @@ async function createHostedWalletGatewayCompositionV1(env, dependencies = {}, ba
390
391
  registrationContext: dependencies.registrationContext,
391
392
  signerDatabase: env.SIGNER_DB,
392
393
  signerStorageNamespace: scope.namespace,
393
- routeExtensions: [new WalletAuthorizationRelocationRoutes({
394
- database: env.SIGNER_DB,
395
- scope,
396
- directory: env.WALLET_CONSOLE
397
- }), new WalletPlacementRoutes({
398
- emailOtp: service.relocationEmailOtp,
399
- database: env.SIGNER_DB,
400
- scope,
401
- tenantId: tenantId.value,
402
- allowedOrigins: readStagingHostedWalletOrigins(env).map(parseSessionOrigin),
403
- authorizationSessions: service.authorizationSessions,
404
- directory: env.WALLET_CONSOLE
405
- })],
394
+ routeExtensions: [
395
+ new WalletAuthorizationRelocationRoutes({
396
+ database: env.SIGNER_DB,
397
+ scope,
398
+ directory: env.WALLET_CONSOLE
399
+ }),
400
+ new WalletPlacementRoutes({
401
+ emailOtp: service.relocationEmailOtp,
402
+ database: env.SIGNER_DB,
403
+ scope,
404
+ tenantId: tenantId.value,
405
+ allowedOrigins: readStagingHostedWalletOrigins(env).map(parseSessionOrigin),
406
+ authorizationSessions: service.authorizationSessions,
407
+ directory: env.WALLET_CONSOLE
408
+ }),
409
+ ...createSpeakToSignVerifyRouteExtensions(readSpeakToSignVerifyConfig((name) => readEnvironmentString(env, name)), {
410
+ authorizationSessions: service.authorizationSessions,
411
+ walletRegistration: service.walletRegistration,
412
+ fetch: globalThis.fetch.bind(globalThis)
413
+ })
414
+ ],
406
415
  router: {
407
416
  healthz: true,
408
417
  readyz: true,
@@ -0,0 +1,254 @@
1
+ import { isPlainObject } from "../../../shared-ts/src/utils/validation.js";
2
+ import { NEAR_ED25519_MPC_OPERATION_KINDS } from "../../../shared-ts/src/authorization/capabilityKinds.js";
3
+ import { routerAbMpcMaterialActivationRefToWire } from "../../../shared-ts/src/utils/routerAbNormalSigningIdentity.js";
4
+ import { hasExactKeys } from "../../../shared-ts/src/utils/exactKeys.js";
5
+ import { extractBearerCredential } from "../../auth/routerApiKeyAuth.js";
6
+ import { defineRoute } from "../../framework/routeDefinitions.js";
7
+ import { resolveWalletSessionOperationCredentialAdmissionFromContext } from "../../auth/commonRouterUtils.js";
8
+ //#region src/router/domains/speakToSign/speakToSignVerify.ts
9
+ const SPEAK_TO_SIGN_VERIFY_PATH = "/speak-to-sign/v1/verify";
10
+ /** 8 s of 16 kHz PCM16 is 256,000 bytes, about 341 KB in base64, with room for the fields. */
11
+ const SPEAK_TO_SIGN_VERIFY_MAX_BODY_BYTES = 400 * 1024;
12
+ /** The verifier calls two remote models; the whole flow has a 10 s budget. */
13
+ const OUTLAYER_TIMEOUT_MS = 2e4;
14
+ const OUTLAYER_RESOURCE_LIMITS = Object.freeze({
15
+ max_instructions: 1e10,
16
+ max_memory_mb: 128,
17
+ max_execution_seconds: 15
18
+ });
19
+ const ENV_NAMES = [
20
+ "SPEAK_TO_SIGN_OUTLAYER_BASE_URL",
21
+ "SPEAK_TO_SIGN_OUTLAYER_PROJECT",
22
+ "SPEAK_TO_SIGN_OUTLAYER_VERSION_KEY",
23
+ "SPEAK_TO_SIGN_OUTLAYER_PAYMENT_KEY",
24
+ "SPEAK_TO_SIGN_OUTLAYER_SECRETS_PROFILE",
25
+ "SPEAK_TO_SIGN_OUTLAYER_SECRETS_ACCOUNT_ID"
26
+ ];
27
+ /** Null when no Speak-to-Sign variable is set. A partial set is a deployment error. */
28
+ function readSpeakToSignVerifyConfig(read) {
29
+ const values = ENV_NAMES.map((name) => read(name)?.trim() || "");
30
+ if (values.every((value) => value === "")) return null;
31
+ const missing = ENV_NAMES.filter((_, index) => values[index] === "");
32
+ if (missing.length > 0) throw new Error(`Speak-to-Sign verify needs ${missing.join(", ")}`);
33
+ const [outlayerBaseUrl, project, versionKey, paymentKey, profile, accountId] = values;
34
+ return {
35
+ outlayerBaseUrl,
36
+ project,
37
+ versionKey,
38
+ paymentKey,
39
+ secretsRef: {
40
+ profile,
41
+ accountId
42
+ }
43
+ };
44
+ }
45
+ function createSpeakToSignVerifyRouteExtensions(config, dependencies) {
46
+ if (!config) return [];
47
+ return [new SpeakToSignVerifyRoutes(resolveConfig(config), dependencies)];
48
+ }
49
+ function resolveConfig(config) {
50
+ let base;
51
+ try {
52
+ base = new URL(config.outlayerBaseUrl);
53
+ } catch {
54
+ throw new Error("Speak-to-Sign OutLayer base URL is invalid");
55
+ }
56
+ if (base.protocol !== "https:" || base.username || base.password || base.search || base.hash) throw new Error("Speak-to-Sign OutLayer base URL must be a plain HTTPS URL");
57
+ if (!/^[a-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(config.project)) throw new Error("Speak-to-Sign OutLayer project must be owner/project");
58
+ for (const [label, value] of [
59
+ ["version key", config.versionKey],
60
+ ["payment key", config.paymentKey],
61
+ ["secrets profile", config.secretsRef.profile],
62
+ ["secrets account", config.secretsRef.accountId]
63
+ ]) if (!value || value.trim() !== value) throw new Error(`Speak-to-Sign OutLayer ${label} is required`);
64
+ return {
65
+ callUrl: `${base.origin}${base.pathname.replace(/\/+$/, "")}/call/${config.project}`,
66
+ versionKey: config.versionKey,
67
+ paymentKey: config.paymentKey,
68
+ secretsRef: {
69
+ profile: config.secretsRef.profile,
70
+ account_id: config.secretsRef.accountId
71
+ }
72
+ };
73
+ }
74
+ var SpeakToSignVerifyRoutes = class {
75
+ kind = "fetch_route_extension";
76
+ id = "speak_to_sign_verify";
77
+ routes = [defineRoute({
78
+ id: "speak_to_sign_verify",
79
+ surface: "relay",
80
+ method: "POST",
81
+ path: SPEAK_TO_SIGN_VERIFY_PATH,
82
+ auth: {
83
+ plane: "public",
84
+ rationale: "The handler requires an active Wallet Session operation credential."
85
+ },
86
+ metering: { kind: "none" },
87
+ summary: "Run the pinned OutLayer voice verifier for a spoken payment"
88
+ })];
89
+ now;
90
+ constructor(config, dependencies) {
91
+ this.config = config;
92
+ this.dependencies = dependencies;
93
+ this.now = dependencies.now ?? Date.now;
94
+ }
95
+ async handleFetchRoute(input) {
96
+ const startedAtMs = this.now();
97
+ const subject = await this.authenticate(input.request, startedAtMs);
98
+ if (!subject) return failure(401, "unauthorized");
99
+ const { walletId } = subject;
100
+ if (this.dependencies.rateLimit) {
101
+ if (!(await this.dependencies.rateLimit({ walletId })).allowed) return failure(429, "rate_limited");
102
+ }
103
+ const body = await readBoundedText(input.request, SPEAK_TO_SIGN_VERIFY_MAX_BODY_BYTES);
104
+ if (body === "too_large") return failure(413, "body_too_large");
105
+ const verifierInput = parseVerifyBody(body);
106
+ if (verifierInput === null) return failure(400, "invalid_body");
107
+ const owner = await this.sessionNearAccount(subject);
108
+ if (owner === null) return failure(403, "owner_unavailable");
109
+ if (verifierInput.owner !== owner) return failure(403, "owner_mismatch");
110
+ return await this.callVerifier(verifierInput, input.logger, startedAtMs);
111
+ }
112
+ async authenticate(request, nowMs) {
113
+ const token = extractBearerCredential(request.headers);
114
+ if (!token) return null;
115
+ const sessions = this.dependencies.authorizationSessions;
116
+ const context = await sessions.readWalletSessionAuthorizationV2ByOperationCredential({
117
+ tenantId: sessions.tenantId,
118
+ token,
119
+ nowMs
120
+ });
121
+ if (!context) return null;
122
+ if (resolveWalletSessionOperationCredentialAdmissionFromContext({
123
+ context,
124
+ nowMs,
125
+ operation: {
126
+ keyFamily: "ed25519",
127
+ operationKind: NEAR_ED25519_MPC_OPERATION_KINDS.signTransaction
128
+ }
129
+ }).kind !== "admitted") return null;
130
+ return {
131
+ walletId: context.authorization.session.walletId,
132
+ ed25519: context.authority.signerActivations.ed25519?.materialActivation ?? null
133
+ };
134
+ }
135
+ /** The NEAR account of the session's Ed25519 signer, or null when the wallet has none. */
136
+ async sessionNearAccount(subject) {
137
+ if (!subject.ed25519) return null;
138
+ const resolved = await this.dependencies.walletRegistration.resolveEd25519MaterialActivation({
139
+ walletId: String(subject.walletId),
140
+ materialActivation: routerAbMpcMaterialActivationRefToWire(subject.ed25519)
141
+ });
142
+ return resolved.ok ? resolved.nearAccountId : null;
143
+ }
144
+ async callVerifier(verifierInput, logger, startedAtMs) {
145
+ const outlayerStartedAtMs = this.now();
146
+ let response;
147
+ try {
148
+ response = await this.dependencies.fetch(this.config.callUrl, {
149
+ method: "POST",
150
+ headers: {
151
+ "content-type": "application/json",
152
+ "x-payment-key": this.config.paymentKey
153
+ },
154
+ body: JSON.stringify({
155
+ input: verifierInput,
156
+ async: false,
157
+ version_key: this.config.versionKey,
158
+ secrets_ref: this.config.secretsRef,
159
+ resource_limits: OUTLAYER_RESOURCE_LIMITS
160
+ }),
161
+ signal: AbortSignal.timeout(OUTLAYER_TIMEOUT_MS)
162
+ });
163
+ } catch (error) {
164
+ const timedOut = error instanceof DOMException && error.name === "TimeoutError";
165
+ logger.warn("[speak-to-sign] OutLayer call failed", { timedOut });
166
+ return failure(timedOut ? 504 : 502, timedOut ? "verifier_timeout" : "verifier_unreachable");
167
+ }
168
+ const outlayerMs = this.now() - outlayerStartedAtMs;
169
+ if (!response.ok) {
170
+ logger.warn("[speak-to-sign] OutLayer refused the call", { status: response.status });
171
+ return failure(...outlayerStatusFailure(response.status));
172
+ }
173
+ const output = readOutlayerOutput(await response.text());
174
+ if (output === null) {
175
+ logger.warn("[speak-to-sign] OutLayer returned no verifier output");
176
+ return failure(502, "verifier_failed");
177
+ }
178
+ return Response.json({
179
+ ok: true,
180
+ output,
181
+ timing: {
182
+ outlayerMs,
183
+ serverMs: this.now() - startedAtMs
184
+ }
185
+ }, { headers: { "cache-control": "no-store" } });
186
+ }
187
+ };
188
+ /** Body: `{ input }`, where `input` is the verifier input object. The server does not read it. */
189
+ function parseVerifyBody(text) {
190
+ let raw;
191
+ try {
192
+ raw = JSON.parse(text);
193
+ } catch {
194
+ return null;
195
+ }
196
+ if (!hasExactKeys(raw, ["input"]) || !isPlainObject(raw.input)) return null;
197
+ return raw.input;
198
+ }
199
+ /** The verifier's own output, unchanged, or null when the run did not complete. */
200
+ function readOutlayerOutput(text) {
201
+ let raw;
202
+ try {
203
+ raw = JSON.parse(text);
204
+ } catch {
205
+ return null;
206
+ }
207
+ if (!isPlainObject(raw) || raw.error != null) return null;
208
+ if (typeof raw.status === "string" && raw.status !== "completed") return null;
209
+ return raw.output ?? null;
210
+ }
211
+ function outlayerStatusFailure(status) {
212
+ if (status === 401 || status === 402 || status === 403) return [503, "verifier_payment_unavailable"];
213
+ if (status === 429) return [503, "verifier_busy"];
214
+ if (status >= 400 && status < 500) return [502, "verifier_rejected_request"];
215
+ return [502, "verifier_failed"];
216
+ }
217
+ async function readBoundedText(request, maximumBytes) {
218
+ const declared = Number(request.headers.get("content-length") ?? "0");
219
+ if (Number.isFinite(declared) && declared > maximumBytes) return "too_large";
220
+ if (!request.body) return "";
221
+ const reader = request.body.getReader();
222
+ const chunks = [];
223
+ let length = 0;
224
+ for (;;) {
225
+ const { done, value } = await reader.read();
226
+ if (done) break;
227
+ length += value.length;
228
+ if (length > maximumBytes) {
229
+ await reader.cancel();
230
+ return "too_large";
231
+ }
232
+ chunks.push(value);
233
+ }
234
+ const bytes = new Uint8Array(length);
235
+ let offset = 0;
236
+ for (const chunk of chunks) {
237
+ bytes.set(chunk, offset);
238
+ offset += chunk.length;
239
+ }
240
+ return new TextDecoder().decode(bytes);
241
+ }
242
+ function failure(status, code) {
243
+ return Response.json({
244
+ ok: false,
245
+ code
246
+ }, {
247
+ status,
248
+ headers: { "cache-control": "no-store" }
249
+ });
250
+ }
251
+ //#endregion
252
+ export { SPEAK_TO_SIGN_VERIFY_MAX_BODY_BYTES, SPEAK_TO_SIGN_VERIFY_PATH, createSpeakToSignVerifyRouteExtensions, readSpeakToSignVerifyConfig };
253
+
254
+ //# sourceMappingURL=speakToSignVerify.js.map
@@ -74,6 +74,12 @@ export interface CloudflareD1GatewayBaseEnv extends Readonly<Record<string, unkn
74
74
  readonly EMAIL_OTP_LOCKOUT_TTL_MS?: string;
75
75
  readonly EMAIL_OTP_GOOGLE_REGISTRATION_ATTEMPT_RATE_LIMIT_MAX?: string;
76
76
  readonly EMAIL_OTP_GOOGLE_REGISTRATION_ATTEMPT_RATE_LIMIT_WINDOW_MS?: string;
77
+ readonly SPEAK_TO_SIGN_OUTLAYER_BASE_URL?: string;
78
+ readonly SPEAK_TO_SIGN_OUTLAYER_PROJECT?: string;
79
+ readonly SPEAK_TO_SIGN_OUTLAYER_VERSION_KEY?: string;
80
+ readonly SPEAK_TO_SIGN_OUTLAYER_PAYMENT_KEY?: string;
81
+ readonly SPEAK_TO_SIGN_OUTLAYER_SECRETS_PROFILE?: string;
82
+ readonly SPEAK_TO_SIGN_OUTLAYER_SECRETS_ACCOUNT_ID?: string;
77
83
  }
78
84
  export declare function readStagingHostedWalletOrigins(env: Pick<CloudflareD1GatewayBaseEnv, 'HOSTED_WALLET_ORIGINS'>): string[];
79
85
  export interface CloudflareD1GatewayEnv extends CloudflareD1GatewayBaseEnv {
@@ -0,0 +1,40 @@
1
+ import type { WalletId } from '../../../../../shared-ts/src/utils/domainIds';
2
+ import type { RouterApiAuthorizationSessionService, RouterApiWalletRegistrationService } from '../../framework/authServicePort';
3
+ import type { RouterApiRouteExtension } from '../../framework/routeExtensions';
4
+ export declare const SPEAK_TO_SIGN_VERIFY_PATH = "/speak-to-sign/v1/verify";
5
+ /** 8 s of 16 kHz PCM16 is 256,000 bytes, about 341 KB in base64, with room for the fields. */
6
+ export declare const SPEAK_TO_SIGN_VERIFY_MAX_BODY_BYTES: number;
7
+ /** The route exists only when the server configuration holds every field. */
8
+ export type SpeakToSignVerifyConfig = Readonly<{
9
+ /** For example `https://api.outlayer.fastnear.com`. */
10
+ outlayerBaseUrl: string;
11
+ /** `owner/project`, for example `seams-v1.testnet/voice-verifier`. */
12
+ project: string;
13
+ /** The WASM hash of the pinned verifier version. */
14
+ versionKey: string;
15
+ /** The OutLayer payment key. It goes only into the X-Payment-Key header. */
16
+ paymentKey: string;
17
+ secretsRef: Readonly<{
18
+ profile: string;
19
+ accountId: string;
20
+ }>;
21
+ }>;
22
+ export type SpeakToSignVerifyRateLimit = (input: {
23
+ readonly walletId: WalletId;
24
+ }) => Promise<{
25
+ readonly allowed: boolean;
26
+ }>;
27
+ export type SpeakToSignVerifyDependencies = Readonly<{
28
+ authorizationSessions: Pick<RouterApiAuthorizationSessionService, 'tenantId' | 'readWalletSessionAuthorizationV2ByOperationCredential'>;
29
+ /** Resolves the NEAR account of the session's Ed25519 signer for the owner check. */
30
+ walletRegistration: Pick<RouterApiWalletRegistrationService, 'resolveEd25519MaterialActivation'>;
31
+ fetch: typeof fetch;
32
+ rateLimit?: SpeakToSignVerifyRateLimit;
33
+ now?: () => number;
34
+ }>;
35
+ declare const ENV_NAMES: readonly ['SPEAK_TO_SIGN_OUTLAYER_BASE_URL', 'SPEAK_TO_SIGN_OUTLAYER_PROJECT', 'SPEAK_TO_SIGN_OUTLAYER_VERSION_KEY', 'SPEAK_TO_SIGN_OUTLAYER_PAYMENT_KEY', 'SPEAK_TO_SIGN_OUTLAYER_SECRETS_PROFILE', 'SPEAK_TO_SIGN_OUTLAYER_SECRETS_ACCOUNT_ID'];
36
+ /** Null when no Speak-to-Sign variable is set. A partial set is a deployment error. */
37
+ export declare function readSpeakToSignVerifyConfig(read: (name: (typeof ENV_NAMES)[number]) => string | undefined): SpeakToSignVerifyConfig | null;
38
+ export declare function createSpeakToSignVerifyRouteExtensions(config: SpeakToSignVerifyConfig | null, dependencies: SpeakToSignVerifyDependencies): readonly RouterApiRouteExtension[];
39
+ export {};
40
+ //# sourceMappingURL=speakToSignVerify.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seams/wallet-server",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "type": "module",
5
5
  "description": "Seams server TypeScript SDK for NEAR and EVM-family wallets",
6
6
  "main": "./dist/esm/index.js",