@haven_ai/signer 0.0.0-dev.202609250651.afc2091 → 0.0.0-dev.202609250737.3bd5a51

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
@@ -61,7 +61,7 @@ It exposes four stdio MCP tools, all sign-only:
61
61
 
62
62
  | Tool | Does | Emits |
63
63
  |---|---|---|
64
- | `haven_sign` | Sign one payment. Preferred form is `{ payment_id }` alone — the signer fetches the exact payload itself. Signs only Haven-prepared payloads (#3272): a direct-payment `PackedUserOperation` from this signer's own delegate account whose only call redeems a delegation made to that account, an erc7710 settlement child or EIP-3009 funding leg against a Haven-signed context (which it records and binds); other typed data is refused (`TYPED_DATA_NOT_ALLOWED`) | `{ signature }` or `{ signature, x402_binding }` |
64
+ | `haven_sign` | Sign one payment. Preferred form is `{ payment_id }` alone — the signer fetches the exact payload itself. Signs only Haven-prepared payloads (#3272, #3281): a direct-payment `PackedUserOperation` from this signer's own delegate account whose only call redeems a delegation made to that account, or — against a Haven-signed context, which it records and binds — an EIP-3009 funding leg of that same shape paying this signer's own delegate EOA, or an erc7710 settlement child from this signer's own account; other typed data is refused (`TYPED_DATA_NOT_ALLOWED`) | `{ signature }` or `{ signature, x402_binding }` |
65
65
  | `haven_sign_x402` | One-shot x402: funding signature **and** the merchant header in a single local call (`haven_sign` + `haven_x402_sign_header`). `{ payment_id }` alone is the preferred call | `{ signature, x402_binding, payment_header, accepted }` |
66
66
  | `haven_x402_sign_header` | Build + sign the EIP-3009 merchant payment header, only when the fresh merchant `payment_required` matches the recorded `x402_binding` | `{ payment_header, accepted }` |
67
67
  | `haven_sign_sweep_delegate` | Sign a Haven-prepared gasless EIP-3009 sweep that recovers stranded funds from the delegate wallet back to your own account. Never broadcasts | `{ signature }` |
@@ -105,8 +105,11 @@ it signs whatever typed data it is handed. The allowlist lives in the MCP tool
105
105
  layer: `haven_sign` signs typed data only when it is a bound direct-payment
106
106
  `PackedUserOperation` (below) or an x402 payload against a Haven-signed context,
107
107
  refuses anything else with `TYPED_DATA_NOT_ALLOWED`, and answers a bare
108
- `payload_hash` with `BARE_HASH_REFUSED`. An embedder that calls the core
109
- directly owns that check itself.
108
+ `payload_hash` with `BARE_HASH_REFUSED`. An embedder that calls
109
+ `signDelegationTypedData` directly owns that check itself. The core's
110
+ `signX402FundingTypedData` is not a verbatim primitive: since #3281 it runs
111
+ the x402 shape checks itself (a guarded funding leg or a verified settlement
112
+ child only), whoever calls it.
110
113
 
111
114
  **The unbound-branch allowlist (#3272).** Without an x402 context, `haven_sign`
112
115
  signs typed data only when ALL of these hold; otherwise it refuses, with no
@@ -144,9 +147,13 @@ operation, never that Haven prepared it — provenance is `payment_id` (below).
144
147
  It runs whether the typed data arrived as a tool argument or by the
145
148
  `payment_id` fetch; a fetched direct context that is not a
146
149
  `PackedUserOperation` at all is refused by the same check. The x402 EIP-3009
147
- bridge's funding leg keeps its own digest check against the Haven-signed
148
- expected context in this signer (the SDK's `signForData` runs this binding
149
- check there too). The check exists because this typed data is
150
+ bridge's funding leg is checked against the Haven-signed expected context AND,
151
+ since #3281, by this same binding check against the declared UserOp hash, the
152
+ direct-payment allowlist and a recipient pin (a transfer of the quoted amount
153
+ to this signer's own delegate EOA). A settlement child must be re-delegated
154
+ from this signer's own account, never a ROOT grant. Anything else on the x402
155
+ arm is `TYPED_DATA_NOT_ALLOWED`, however validly Haven's binding key declared
156
+ it. The check exists because this typed data is
150
157
  multi-KB and can reach the signer through a language model relaying it by
151
158
  hand: one corrupted character used to produce a valid-looking signature over
152
159
  the wrong digest, surfacing only as an opaque `AA24 signature error` from the
package/dist/cli.cjs CHANGED
@@ -634,7 +634,17 @@ var toolDescriptions = {
634
634
  haven_sign_sweep_delegate: SIGN_SWEEP_DELEGATE_DESCRIPTION
635
635
  };
636
636
  async function signFundingLeg(signer, expected, typedData) {
637
- return signer.signX402FundingTypedData(typedData, expected);
637
+ try {
638
+ return await signer.signX402FundingTypedData(typedData, expected);
639
+ } catch (err) {
640
+ if (err instanceof edge.HavenTypedDataRefusedError && !(err instanceof HavenTypedDataNotAllowedError)) {
641
+ throw new HavenTypedDataNotAllowedError(err.message);
642
+ }
643
+ if (err instanceof edge.HavenUserOpBindingError) {
644
+ throw new HavenUserOpBindingRefusedError(err.message);
645
+ }
646
+ throw err;
647
+ }
638
648
  }
639
649
  function toExpectedX402(raw) {
640
650
  return {
@@ -1222,7 +1232,7 @@ function createEdgeSigner(delegateKey, options = {}) {
1222
1232
  `Refusing to sign the x402 settlement child: this signer cannot map the network '${expected.network}' to a chain id, so it cannot check which chain the child is scoped to. Update @haven_ai/signer.`
1223
1233
  );
1224
1234
  }
1225
- edge.verifySettlementChild(typedData, {
1235
+ edge.assertOwnSettlementChild(typedData, {
1226
1236
  merchantTo: expected.merchantTo,
1227
1237
  amount: expected.amount,
1228
1238
  asset: expected.asset,
@@ -1232,7 +1242,21 @@ function createEdgeSigner(delegateKey, options = {}) {
1232
1242
  // signed another" class this file exists to catch (#1455 review).
1233
1243
  chainId: settlementChainId,
1234
1244
  expiresAt: expected.expiresAt
1245
+ }, delegateAddress);
1246
+ } else if (edge.isPackedUserOperationTypedData(typedData)) {
1247
+ edge.assertUserOpTypedDataBinding(typedData, expected.payloadHash);
1248
+ edge.assertBoundDirectPaymentUserOp(typedData, delegateAddress);
1249
+ edge.assertFundingLegPaysDelegate(typedData, {
1250
+ delegateAddress,
1251
+ asset: expected.asset,
1252
+ amount: expected.amount
1235
1253
  });
1254
+ } else {
1255
+ throw new edge.HavenTypedDataRefusedError(
1256
+ `This signer's x402 arm signs only a funding-leg PackedUserOperation or an erc7710 settlement Delegation; this typed data is '${String(
1257
+ typedData.primaryType
1258
+ )}'. Refusing, even though its digest is bound by the expected context.`
1259
+ );
1236
1260
  }
1237
1261
  const account = accounts.privateKeyToAccount(delegateKey);
1238
1262
  const signature = await account.signTypedData(
@@ -1639,7 +1663,7 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
1639
1663
 
1640
1664
  // src/server.ts
1641
1665
  var SIGNER_NAME = "@haven_ai/signer";
1642
- var SIGNER_VERSION = "0.0.0-dev.202609250651.afc2091";
1666
+ var SIGNER_VERSION = "0.0.0-dev.202609250737.3bd5a51";
1643
1667
  async function resolveSignerRuntime(options = {}) {
1644
1668
  assertSupportedNodeVersion(options.nodeVersion);
1645
1669
  if (options.delegateKey) {