@haven_ai/signer 0.1.28-alpha.0 → 0.1.30-alpha.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/dist/cli.cjs CHANGED
@@ -204,6 +204,7 @@ function createEdgeSigner(delegateKey, options = {}) {
204
204
  },
205
205
  signX402FundingHash(hash, expected) {
206
206
  assertExpectedBinding(hash, expected, options.x402BindingSigner, "hash");
207
+ assertPayerMatchesDelegate(expected, delegateAddress, options.agentId);
207
208
  const signature = signAndVerify(hash);
208
209
  const x402Binding = crypto.randomUUID();
209
210
  x402Bindings.set(x402Binding, { ...expected });
@@ -211,6 +212,7 @@ function createEdgeSigner(delegateKey, options = {}) {
211
212
  },
212
213
  async signX402FundingTypedData(typedData, expected) {
213
214
  assertExpectedBinding(expected.payloadHash, expected, options.x402BindingSigner, "typed-data");
215
+ assertPayerMatchesDelegate(expected, delegateAddress, options.agentId);
214
216
  const digest = viem.hashTypedData(typedData);
215
217
  if (digest.toLowerCase() !== expected.typedDataHash?.toLowerCase()) {
216
218
  throw new sdk.HavenSigningError(
@@ -380,7 +382,7 @@ function assertExpectedShape(expected) {
380
382
  throw new sdk.HavenSigningError("x402 expected funding context is required before signing a merchant header.");
381
383
  }
382
384
  }
383
- var SUPPORTED_X402_EXPECTED_VERSIONS = [1, 2];
385
+ var SUPPORTED_X402_EXPECTED_VERSIONS = [1, 2, 3];
384
386
  var SUPPORTED_SWEEP_BINDING_VERSIONS = [1];
385
387
  function assertSupportedBindingVersion(received, supported, context) {
386
388
  if (supported.includes(received)) return;
@@ -433,9 +435,11 @@ function assertExpectedBinding(payloadHash, expected, trustedSigner, mode = "has
433
435
  asset: expected.asset,
434
436
  network: expected.network,
435
437
  expiresAt: expected.expiresAt,
436
- typedDataHash: expected.typedDataHash
438
+ typedDataHash: expected.typedDataHash,
439
+ payerDelegate: expected.payerDelegate,
440
+ payerAgentId: expected.payerAgentId
437
441
  });
438
- const expectedVersion = expected.typedDataHash ? 2 : 1;
442
+ const expectedVersion = expected.payerDelegate ? 3 : expected.typedDataHash ? 2 : 1;
439
443
  if (expected.auth?.version !== expectedVersion || expected.auth.message !== message) {
440
444
  throw new sdk.HavenSigningError("x402 expected context authentication message is invalid.");
441
445
  }
@@ -446,6 +450,15 @@ function assertExpectedBinding(payloadHash, expected, trustedSigner, mode = "has
446
450
  throw new sdk.HavenSigningError("x402 expected context signature could not be verified.");
447
451
  }
448
452
  }
453
+ function assertPayerMatchesDelegate(expected, delegateAddress, localAgentId) {
454
+ if (!expected.payerDelegate) return;
455
+ if (sameAddress(expected.payerDelegate, delegateAddress)) return;
456
+ const quoteAgent = expected.payerAgentId ?? "unknown";
457
+ const localAgent = localAgentId ?? "unknown";
458
+ throw new sdk.HavenSigningError(
459
+ `This quote belongs to a DIFFERENT agent: the quote was created for agent ${quoteAgent} (delegate ${expected.payerDelegate}), but this signer holds agent ${localAgent} (delegate ${delegateAddress}). A long-lived host is holding stale credentials \u2014 its session authenticates as the old agent while the signer on disk belongs to the new one. Nothing was signed. Restart the host so it re-reads its wiring, then re-quote.`
460
+ );
461
+ }
449
462
  function assertX402PaymentWindowOpen(expected) {
450
463
  if (!expected.expiresAt) return;
451
464
  const expiresAtMs = Date.parse(expected.expiresAt);
@@ -585,6 +598,11 @@ var x402ExpectedShape = {
585
598
  // typed data the account validates; the signer refuses to raw-sign the bare
586
599
  // hash when it is present, and refuses to sign typed data when it is absent.
587
600
  typed_data_hash: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "typed_data_hash must be a 0x-prefixed hex string").optional(),
601
+ // #1690: present on a v3 context. The delegate this quote was created FOR —
602
+ // the signer refuses to sign when it is not its own. Inside the Haven-signed
603
+ // message, so stripping it here would only break the binding signature.
604
+ payer_delegate: v3.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "payer_delegate must be a 0x-prefixed address").optional(),
605
+ payer_agent_id: v3.z.string().min(1).optional(),
588
606
  auth: v3.z.object({
589
607
  // Open at the boundary, enforced in the signer — see bindingVersionSchema.
590
608
  version: bindingVersionSchema,
@@ -731,6 +749,8 @@ function toExpectedX402(raw) {
731
749
  paymentId: raw.payment_id,
732
750
  payloadHash: raw.payload_hash,
733
751
  typedDataHash: raw.typed_data_hash,
752
+ payerDelegate: raw.payer_delegate,
753
+ payerAgentId: raw.payer_agent_id,
734
754
  resourceUrl: raw.resource_url,
735
755
  merchantTo: raw.merchant_to,
736
756
  amount: raw.amount,
@@ -1186,7 +1206,7 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
1186
1206
 
1187
1207
  // src/server.ts
1188
1208
  var SIGNER_NAME = "@haven_ai/signer";
1189
- var SIGNER_VERSION = "0.1.28-alpha.0";
1209
+ var SIGNER_VERSION = "0.1.30-alpha.0";
1190
1210
  async function resolveSignerRuntime(options = {}) {
1191
1211
  assertSupportedNodeVersion(options.nodeVersion);
1192
1212
  if (options.delegateKey) {
@@ -1199,7 +1219,11 @@ async function resolveSignerRuntime(options = {}) {
1199
1219
  const creds = await loadSignerCredentials(options.credentialsPath);
1200
1220
  return {
1201
1221
  signer: createEdgeSigner(creds.delegateKey, {
1202
- x402BindingSigner: options.x402BindingSigner ?? creds.x402BindingSigner ?? process.env.HAVEN_X402_BINDING_SIGNER
1222
+ x402BindingSigner: options.x402BindingSigner ?? creds.x402BindingSigner ?? process.env.HAVEN_X402_BINDING_SIGNER,
1223
+ // #1690: the signer's own agent id, so a payer-mismatch refusal can name
1224
+ // both sides. The bare-delegateKey path above has no credential file and
1225
+ // therefore no agent id — the guard still works there, on addresses.
1226
+ agentId: creds.agentId
1203
1227
  }),
1204
1228
  credentials: creds
1205
1229
  };