@haven_ai/signer 0.0.0-dev.202609242134.f497e3b → 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 +17 -7
- package/dist/cli.cjs +38 -12
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +39 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +40 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +42 -16
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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,
|
|
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
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
@@ -431,7 +438,10 @@ audit write (disk full, read-only, two appends racing at the bound) is
|
|
|
431
438
|
reported on stderr and never fails the signing call that already produced
|
|
432
439
|
its signature — so the consent block's
|
|
433
440
|
"appended for every signing operation" is a best-effort promise since #3172,
|
|
434
|
-
kept unchanged in text
|
|
441
|
+
kept unchanged in text (editing consent copy does NOT move the consent hash —
|
|
442
|
+
that covers identity, tool names and the surface version, as the consent
|
|
443
|
+
screen section above already says; #3279 rewords the block without
|
|
444
|
+
re-prompting anyone). The
|
|
435
445
|
credential check itself is unchanged in wording and still judges a symlinked
|
|
436
446
|
credential by its target. The
|
|
437
447
|
`payload_hash` argument itself is bounded on the tool schema to a 32-byte hash
|
package/dist/cli.cjs
CHANGED
|
@@ -619,12 +619,13 @@ var SIGN_X402_DESCRIPTION = [
|
|
|
619
619
|
].join(" ");
|
|
620
620
|
var SIGN_SWEEP_DELEGATE_DESCRIPTION = [
|
|
621
621
|
"Sign a Haven-prepared gasless USDC sweep that recovers stranded funds from the delegate",
|
|
622
|
-
"wallet back to
|
|
623
|
-
"never broadcasts \u2014 it returns only an EIP-3009 signature that Haven's relayer submits and",
|
|
622
|
+
"wallet back to the agent's account (Haven wallet). The delegate key never leaves this process and this",
|
|
623
|
+
"tool never broadcasts \u2014 it returns only an EIP-3009 signature that Haven's relayer submits and",
|
|
624
624
|
"pays gas for. Pass the authorization and expected_auth returned by the hosted",
|
|
625
|
-
"haven_sweep_delegate tool. The signer verifies Haven authored the authorization
|
|
626
|
-
"
|
|
627
|
-
"
|
|
625
|
+
"haven_sweep_delegate tool. The signer verifies Haven authored the authorization; the",
|
|
626
|
+
"destination is checked against the agent's account (Haven wallet) from the local credential",
|
|
627
|
+
"when the credential carries one, and otherwise rests on Haven's binding signature. Then it",
|
|
628
|
+
"returns { signature } to hand back to mcp__haven__haven_sweep_delegate to complete recovery."
|
|
628
629
|
].join(" ");
|
|
629
630
|
var toolDescriptions = {
|
|
630
631
|
haven_sign: SIGN_DESCRIPTION,
|
|
@@ -633,7 +634,17 @@ var toolDescriptions = {
|
|
|
633
634
|
haven_sign_sweep_delegate: SIGN_SWEEP_DELEGATE_DESCRIPTION
|
|
634
635
|
};
|
|
635
636
|
async function signFundingLeg(signer, expected, typedData) {
|
|
636
|
-
|
|
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
|
+
}
|
|
637
648
|
}
|
|
638
649
|
function toExpectedX402(raw) {
|
|
639
650
|
return {
|
|
@@ -809,7 +820,8 @@ function createToolHandlers(signer, options = {}) {
|
|
|
809
820
|
const result = await signer.signSweepAuthorization({
|
|
810
821
|
authorization: args.authorization,
|
|
811
822
|
expectedAuth: args.expected_auth,
|
|
812
|
-
// Cross-check `to` against the
|
|
823
|
+
// Cross-check `to` against the agent's account (Haven wallet) from the
|
|
824
|
+
// local credential, when the credential carries one.
|
|
813
825
|
expectedSafe: options.audit?.accountAddress
|
|
814
826
|
});
|
|
815
827
|
await auditSigning(
|
|
@@ -1028,8 +1040,8 @@ function renderSignerConsentBlock(input, hash) {
|
|
|
1028
1040
|
lines.push("payload from Haven, authenticated with the agent credential stored next to");
|
|
1029
1041
|
lines.push("this signer's key file - it never sends the key, a signature, or anything");
|
|
1030
1042
|
lines.push("else outbound, and it cannot show a live allowance summary.");
|
|
1031
|
-
lines.push("
|
|
1032
|
-
lines.push("pause or revoke agent authority outside this signer.");
|
|
1043
|
+
lines.push("The agent's signed budget delegation is the real spend gate, and the wallet");
|
|
1044
|
+
lines.push("owner can pause or revoke agent authority outside this signer.");
|
|
1033
1045
|
lines.push("");
|
|
1034
1046
|
lines.push("Tools this signer will expose to your agent runtime (one line each; the");
|
|
1035
1047
|
lines.push("full agent-facing descriptions are what the runtime sees):");
|
|
@@ -1220,7 +1232,7 @@ function createEdgeSigner(delegateKey, options = {}) {
|
|
|
1220
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.`
|
|
1221
1233
|
);
|
|
1222
1234
|
}
|
|
1223
|
-
edge.
|
|
1235
|
+
edge.assertOwnSettlementChild(typedData, {
|
|
1224
1236
|
merchantTo: expected.merchantTo,
|
|
1225
1237
|
amount: expected.amount,
|
|
1226
1238
|
asset: expected.asset,
|
|
@@ -1230,7 +1242,21 @@ function createEdgeSigner(delegateKey, options = {}) {
|
|
|
1230
1242
|
// signed another" class this file exists to catch (#1455 review).
|
|
1231
1243
|
chainId: settlementChainId,
|
|
1232
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
|
|
1233
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
|
+
);
|
|
1234
1260
|
}
|
|
1235
1261
|
const account = accounts.privateKeyToAccount(delegateKey);
|
|
1236
1262
|
const signature = await account.signTypedData(
|
|
@@ -1314,7 +1340,7 @@ function createEdgeSigner(delegateKey, options = {}) {
|
|
|
1314
1340
|
}
|
|
1315
1341
|
if (expectedSafe && !sameAddress(authorization.to, expectedSafe)) {
|
|
1316
1342
|
throw new edge.HavenSigningError(
|
|
1317
|
-
"Sweep authorization `to` does not match the
|
|
1343
|
+
"Sweep authorization `to` does not match the agent's account (Haven wallet) in the local credential."
|
|
1318
1344
|
);
|
|
1319
1345
|
}
|
|
1320
1346
|
const typedData = edge.buildSweepTypedData(authorization);
|
|
@@ -1637,7 +1663,7 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
|
|
|
1637
1663
|
|
|
1638
1664
|
// src/server.ts
|
|
1639
1665
|
var SIGNER_NAME = "@haven_ai/signer";
|
|
1640
|
-
var SIGNER_VERSION = "0.0.0-dev.
|
|
1666
|
+
var SIGNER_VERSION = "0.0.0-dev.202609250737.3bd5a51";
|
|
1641
1667
|
async function resolveSignerRuntime(options = {}) {
|
|
1642
1668
|
assertSupportedNodeVersion(options.nodeVersion);
|
|
1643
1669
|
if (options.delegateKey) {
|