bsv-mcp 0.3.1 → 0.3.3
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/CHANGELOG.md +12 -0
- package/README.md +51 -1
- package/dist/index.js +257 -39
- package/package.json +1 -1
- package/tools/wallet/droplit.ts +1 -1
- package/tools/wallet/revealDelegation.ts +74 -0
- package/tools/wallet/tools.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# BSV MCP Server Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.3] - 2026-09-06
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Honor `DROPLIT_SITE_URL` for sponsor approval links so staging requests reach the correct owner UI.
|
|
7
|
+
- Build approval links from the configured sponsor and verified wallet identity; reject unsafe site origins.
|
|
8
|
+
|
|
9
|
+
## [0.3.2] - 2026-09-06
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Reveal a human-issued Sigma delegation with the connected agent wallet, using standard certificate acquisition and proof.
|
|
13
|
+
- Reject redirects, automatic payments, and ambiguous mutation retries; submit only verifier-specific keys.
|
|
14
|
+
|
|
3
15
|
## [0.3.1] - 2026-09-05
|
|
4
16
|
|
|
5
17
|
### Added
|
package/README.md
CHANGED
|
@@ -884,6 +884,8 @@ BRC100_WALLET_URL=http://127.0.0.1:3321
|
|
|
884
884
|
BRC100_WALLET_ORIGINATOR=bsv-mcp.local
|
|
885
885
|
DROPLIT_API_URL=https://api.droplit.dev/droplit
|
|
886
886
|
DROPLIT_FAUCET_NAME=your-sponsor-slug
|
|
887
|
+
# Optional: set explicitly when the owner approval UI is hosted elsewhere.
|
|
888
|
+
DROPLIT_SITE_URL=https://droplit.dev
|
|
887
889
|
```
|
|
888
890
|
|
|
889
891
|
Set both sponsor values explicitly. The URL includes the server's configured
|
|
@@ -893,8 +895,15 @@ BRC-100 wallet contexts also support this sponsor pair. `1sat serve` exposes a
|
|
|
893
895
|
wallet stack service; it is not itself a BRC-100 signer RPC endpoint.
|
|
894
896
|
|
|
895
897
|
Call `droplit_getAccess` to inspect your own authorization and quotas. An
|
|
896
|
-
`approval_required` response includes a `
|
|
898
|
+
`approval_required` response includes a link to `DROPLIT_SITE_URL` (default
|
|
899
|
+
`https://droplit.dev`) for manual
|
|
897
900
|
owner review. Do not automatically open, approve, or register to obtain access.
|
|
901
|
+
The site URL must be an HTTPS origin (HTTP is allowed only for loopback),
|
|
902
|
+
without credentials, a path, query, or fragment. For staging, set it to the
|
|
903
|
+
trusted staging frontend origin independently of `DROPLIT_API_URL`. Approval
|
|
904
|
+
links use the configured sponsor and authenticated wallet identity; a server
|
|
905
|
+
response cannot redirect them to another site.
|
|
906
|
+
|
|
898
907
|
Public status and public-key registration do not prove sponsor approval, and
|
|
899
908
|
creating your own faucet requires funding rather than providing free credit.
|
|
900
909
|
Missing quota kinds are unrestricted for authorized users; a configured zero
|
|
@@ -912,3 +921,44 @@ BRC-105 payment handling cannot spend funds; a 402 requires human review. Struct
|
|
|
912
921
|
available quota reset information is retained. `unknown_outcome` means reconcile
|
|
913
922
|
transaction/history before retrying; no idempotency-key support is claimed.
|
|
914
923
|
The legacy `USE_DROPLIT_API=true` wallet mode remains available separately.
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
### Reveal a Sigma agent delegation with the connected wallet
|
|
927
|
+
|
|
928
|
+
After the human owner binds this agent wallet and issues its BRC-169 delegation,
|
|
929
|
+
copy the owner's complete handoff JSON into `wallet_revealDelegation`:
|
|
930
|
+
|
|
931
|
+
- `sigmaOrigin`: the intended Sigma HTTPS origin (HTTP is allowed only for loopback testing).
|
|
932
|
+
- `handoffJSON`: the owner's JSON containing `certificate`, `subjectKeyring`, `revealTo`, and `revelationPath`.
|
|
933
|
+
|
|
934
|
+
The explicit tool invocation acquires the certificate into the connected wallet,
|
|
935
|
+
proves all restrictions to the named Sigma verifier, and POSTs only that verifier's
|
|
936
|
+
keyring. Use the same wallet that supplied the bound identity; its Ed25519 agent
|
|
937
|
+
token cannot perform this step. The encrypted subject keyring stays with the agent
|
|
938
|
+
and is never included in the HTTP request or tool result. This does not grant
|
|
939
|
+
Droplit sponsor permission. No automatic payments, redirects, or POST retries are
|
|
940
|
+
allowed. On `outcome_unknown`, ask the owner to refresh delegation status before
|
|
941
|
+
trying again; certificate acquisition may already have succeeded.
|
|
942
|
+
|
|
943
|
+
For a **local 1Sat CLI wallet** used throughout binding, the existing commands
|
|
944
|
+
provide the same operation. Save the owner package as `handoff.json`, then:
|
|
945
|
+
|
|
946
|
+
```sh
|
|
947
|
+
bun -e 'const h=await Bun.file("handoff.json").json(),c=h.certificate; await Bun.write("acquire.json",JSON.stringify({acquisitionProtocol:"direct",type:c.type,certifier:c.certifier,fields:c.fields,serialNumber:c.serialNumber,revocationOutpoint:c.revocationOutpoint,signature:c.signature,keyringRevealer:"certifier",keyringForSubject:h.subjectKeyring})); await Bun.write("prove.json",JSON.stringify({certificate:c,fieldsToReveal:Object.keys(c.fields),verifier:h.revealTo}));'
|
|
948
|
+
1sat wallet acquire-certificate "$(cat acquire.json)" --json
|
|
949
|
+
1sat wallet prove-certificate "$(cat prove.json)" --json > proof.json
|
|
950
|
+
bun -e 'const p=await Bun.file("proof.json").json(); if(!p.keyringForVerifier) throw new Error("Missing verifier keyring"); await Bun.write("revelation.json",JSON.stringify({keyring:p.keyringForVerifier}));'
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
Set `SIGMA_ORIGIN` to the intended Sigma origin and `REVELATION_PATH` to the
|
|
954
|
+
owner's `/api/agents/{agentId}/delegations/{encodedSerialNumber}/revelation` path;
|
|
955
|
+
percent-encode the base64 serial number as one path segment. Then:
|
|
956
|
+
|
|
957
|
+
```sh
|
|
958
|
+
1sat authfetch POST "${SIGMA_ORIGIN}${REVELATION_PATH}" --body @revelation.json --json
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
Use `@1sat/cli` 0.0.110 or later for stale-session mutation replay protection.
|
|
962
|
+
Keep the same configured wallet and chain for every command. This CLI alternative
|
|
963
|
+
does not use the MCP external wallet connection. The endpoint does not require a
|
|
964
|
+
payment; do not add `--yes` to work around an unexpected 402.
|
package/dist/index.js
CHANGED
|
@@ -207971,9 +207971,9 @@ var require_vary = __commonJS((exports, module) => {
|
|
|
207971
207971
|
if (!field) {
|
|
207972
207972
|
throw new TypeError("field argument is required");
|
|
207973
207973
|
}
|
|
207974
|
-
var
|
|
207975
|
-
for (var j5 = 0;j5 <
|
|
207976
|
-
if (!FIELD_NAME_REGEXP.test(
|
|
207974
|
+
var fields2 = !Array.isArray(field) ? parse8(String(field)) : field;
|
|
207975
|
+
for (var j5 = 0;j5 < fields2.length; j5++) {
|
|
207976
|
+
if (!FIELD_NAME_REGEXP.test(fields2[j5])) {
|
|
207977
207977
|
throw new TypeError("field argument contains an invalid header name");
|
|
207978
207978
|
}
|
|
207979
207979
|
}
|
|
@@ -207982,14 +207982,14 @@ var require_vary = __commonJS((exports, module) => {
|
|
|
207982
207982
|
}
|
|
207983
207983
|
var val = header;
|
|
207984
207984
|
var vals = parse8(header.toLowerCase());
|
|
207985
|
-
if (
|
|
207985
|
+
if (fields2.indexOf("*") !== -1 || vals.indexOf("*") !== -1) {
|
|
207986
207986
|
return "*";
|
|
207987
207987
|
}
|
|
207988
|
-
for (var i = 0;i <
|
|
207989
|
-
var fld =
|
|
207988
|
+
for (var i = 0;i < fields2.length; i++) {
|
|
207989
|
+
var fld = fields2[i].toLowerCase();
|
|
207990
207990
|
if (vals.indexOf(fld) === -1) {
|
|
207991
207991
|
vals.push(fld);
|
|
207992
|
-
val = val ? val + ", " +
|
|
207992
|
+
val = val ? val + ", " + fields2[i] : fields2[i];
|
|
207993
207993
|
}
|
|
207994
207994
|
}
|
|
207995
207995
|
return val;
|
|
@@ -213168,13 +213168,13 @@ var require_StorageKnex = __commonJS((exports) => {
|
|
|
213168
213168
|
delete e2.certificateId;
|
|
213169
213169
|
if (e2.logger != null)
|
|
213170
213170
|
delete e2.logger;
|
|
213171
|
-
const
|
|
213171
|
+
const fields2 = e2.fields;
|
|
213172
213172
|
if (e2.fields != null)
|
|
213173
213173
|
delete e2.fields;
|
|
213174
213174
|
const [id] = await this.toDb(trx)("certificates").insert(e2);
|
|
213175
213175
|
certificate.certificateId = id;
|
|
213176
|
-
if (
|
|
213177
|
-
for (const field of
|
|
213176
|
+
if (fields2 != null) {
|
|
213177
|
+
for (const field of fields2) {
|
|
213178
213178
|
field.certificateId = id;
|
|
213179
213179
|
field.userId = certificate.userId;
|
|
213180
213180
|
await this.insertCertificateField(field, trx);
|
|
@@ -227862,11 +227862,11 @@ var require_querycompiler = __commonJS((exports, module) => {
|
|
|
227862
227862
|
_preValidate() {
|
|
227863
227863
|
const method = this.method;
|
|
227864
227864
|
const verb = hasOwn(methodAliases, method) ? methodAliases[method] : method;
|
|
227865
|
-
const
|
|
227866
|
-
if (!
|
|
227865
|
+
const invalid2 = this.invalidClauses[verb];
|
|
227866
|
+
if (!invalid2)
|
|
227867
227867
|
return;
|
|
227868
|
-
for (let i = 0;i <
|
|
227869
|
-
const clause =
|
|
227868
|
+
for (let i = 0;i < invalid2.length; i++) {
|
|
227869
|
+
const clause = invalid2[i];
|
|
227870
227870
|
const hasNonEmptyGrouped = hasOwn(this.grouped, clause) && this.grouped[clause].length > 0;
|
|
227871
227871
|
const hasNonEmptySingle = hasOwn(this.single, clause) && this.single[clause] != null;
|
|
227872
227872
|
if (hasNonEmptyGrouped || hasNonEmptySingle) {
|
|
@@ -236146,10 +236146,10 @@ var require_mysql = __commonJS((exports, module) => {
|
|
|
236146
236146
|
return;
|
|
236147
236147
|
}
|
|
236148
236148
|
const queryOptions = Object.assign({ sql: obj.sql }, obj.options);
|
|
236149
|
-
connection.query(queryOptions, obj.bindings, function(err, rows,
|
|
236149
|
+
connection.query(queryOptions, obj.bindings, function(err, rows, fields2) {
|
|
236150
236150
|
if (err)
|
|
236151
236151
|
return rejecter(err);
|
|
236152
|
-
obj.response = [rows,
|
|
236152
|
+
obj.response = [rows, fields2];
|
|
236153
236153
|
resolver(obj);
|
|
236154
236154
|
});
|
|
236155
236155
|
});
|
|
@@ -236160,9 +236160,9 @@ var require_mysql = __commonJS((exports, module) => {
|
|
|
236160
236160
|
const { response } = obj;
|
|
236161
236161
|
const { method } = obj;
|
|
236162
236162
|
const rows = response[0];
|
|
236163
|
-
const
|
|
236163
|
+
const fields2 = response[1];
|
|
236164
236164
|
if (obj.output)
|
|
236165
|
-
return obj.output.call(runner, rows,
|
|
236165
|
+
return obj.output.call(runner, rows, fields2);
|
|
236166
236166
|
switch (method) {
|
|
236167
236167
|
case "select":
|
|
236168
236168
|
return rows;
|
|
@@ -236439,9 +236439,9 @@ var require_mariadb = __commonJS((exports, module) => {
|
|
|
236439
236439
|
return;
|
|
236440
236440
|
if (obj.returning) {
|
|
236441
236441
|
const rows = obj.response[0];
|
|
236442
|
-
const
|
|
236442
|
+
const fields2 = obj.response[1];
|
|
236443
236443
|
if (obj.output) {
|
|
236444
|
-
return obj.output.call(runner, rows,
|
|
236444
|
+
return obj.output.call(runner, rows, fields2);
|
|
236445
236445
|
}
|
|
236446
236446
|
return rows;
|
|
236447
236447
|
}
|
|
@@ -242385,8 +242385,8 @@ var require_ShamirWalletManager = __commonJS((exports) => {
|
|
|
242385
242385
|
await this.entropyCollector.collectFromBrowser(element, onProgress);
|
|
242386
242386
|
}
|
|
242387
242387
|
generateUserIdHash(privateKey) {
|
|
242388
|
-
const
|
|
242389
|
-
const hash2 = sdk_1.Hash.sha256(sdk_1.Utils.toArray(
|
|
242388
|
+
const publicKey2 = privateKey.toPublicKey().toString();
|
|
242389
|
+
const hash2 = sdk_1.Hash.sha256(sdk_1.Utils.toArray(publicKey2, "utf8"));
|
|
242390
242390
|
return sdk_1.Utils.toHex(hash2);
|
|
242391
242391
|
}
|
|
242392
242392
|
async createNewWallet(authPayload, onUserSharesReady) {
|
|
@@ -244852,13 +244852,13 @@ var init_storage_pg = __esm(() => {
|
|
|
244852
244852
|
e2.certificateId = undefined;
|
|
244853
244853
|
if (e2.logger)
|
|
244854
244854
|
e2.logger = undefined;
|
|
244855
|
-
const
|
|
244855
|
+
const fields2 = e2.fields;
|
|
244856
244856
|
if (e2.fields)
|
|
244857
244857
|
e2.fields = undefined;
|
|
244858
244858
|
const id = await this.insertRow("certificates", e2, trx);
|
|
244859
244859
|
certificate.certificateId = id;
|
|
244860
|
-
if (
|
|
244861
|
-
for (const field of
|
|
244860
|
+
if (fields2) {
|
|
244861
|
+
for (const field of fields2) {
|
|
244862
244862
|
field.certificateId = id;
|
|
244863
244863
|
field.userId = certificate.userId;
|
|
244864
244864
|
await this.insertCertificateField(field, trx);
|
|
@@ -256042,7 +256042,7 @@ var package_default = {
|
|
|
256042
256042
|
name: "bsv-mcp",
|
|
256043
256043
|
module: "dist/index.js",
|
|
256044
256044
|
type: "module",
|
|
256045
|
-
version: "0.3.
|
|
256045
|
+
version: "0.3.3",
|
|
256046
256046
|
license: "MIT",
|
|
256047
256047
|
author: "satchmo",
|
|
256048
256048
|
description: "A collection of Bitcoin SV (BSV) tools for the Model Context Protocol (MCP) framework",
|
|
@@ -284877,6 +284877,15 @@ function registerUtilsTools(server) {
|
|
|
284877
284877
|
|
|
284878
284878
|
// utils/droplit.ts
|
|
284879
284879
|
init_mod2();
|
|
284880
|
+
function approvalSiteOrigin(value2 = "https://droplit.dev") {
|
|
284881
|
+
const url3 = new URL(value2);
|
|
284882
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url3.hostname);
|
|
284883
|
+
if (url3.username || url3.password || url3.pathname !== "/" || url3.href.includes("?") || url3.href.includes("#") || value2.includes("\\") || !(url3.protocol === "https:" || url3.protocol === "http:" && loopback)) {
|
|
284884
|
+
throw new Error("DROPLIT_SITE_URL requires an HTTPS or HTTP loopback origin without credentials, path, query or fragment");
|
|
284885
|
+
}
|
|
284886
|
+
return url3.origin;
|
|
284887
|
+
}
|
|
284888
|
+
|
|
284880
284889
|
class DroplitError extends Error {
|
|
284881
284890
|
code;
|
|
284882
284891
|
status;
|
|
@@ -284926,9 +284935,11 @@ function httpFailure(status, details = {}) {
|
|
|
284926
284935
|
class DroplitClient {
|
|
284927
284936
|
config;
|
|
284928
284937
|
authFetch;
|
|
284938
|
+
siteOrigin;
|
|
284929
284939
|
wallet;
|
|
284930
284940
|
constructor(config2) {
|
|
284931
284941
|
this.config = config2;
|
|
284942
|
+
this.siteOrigin = approvalSiteOrigin(config2.siteUrl);
|
|
284932
284943
|
if (config2.wallet && config2.authKey)
|
|
284933
284944
|
throw new Error("Configure Droplit wallet or authKey, not both");
|
|
284934
284945
|
const url3 = new URL(config2.apiUrl);
|
|
@@ -285023,6 +285034,7 @@ class DroplitClient {
|
|
|
285023
285034
|
throw new DroplitError("invalid_response", "Sponsor access response did not match the authenticated wallet.");
|
|
285024
285035
|
}
|
|
285025
285036
|
access.approval_path = `/droplit/${encodeURIComponent(this.config.faucetName)}?tab=api&request_key=${encodeURIComponent(identity6)}`;
|
|
285037
|
+
access.approval_url = new URL(access.approval_path, this.siteOrigin).href;
|
|
285026
285038
|
return access;
|
|
285027
285039
|
}
|
|
285028
285040
|
async fund(rawtx) {
|
|
@@ -285093,7 +285105,7 @@ function registerDroplitTools(server, client, disableBroadcasting = false) {
|
|
|
285093
285105
|
...!access.authorized ? {
|
|
285094
285106
|
error: "approval_required",
|
|
285095
285107
|
message: "Ask the sponsor owner to approve this wallet. Copy the approval URL for manual review; do not auto-open or submit approval.",
|
|
285096
|
-
approval_url:
|
|
285108
|
+
approval_url: access.approval_url
|
|
285097
285109
|
} : {}
|
|
285098
285110
|
};
|
|
285099
285111
|
return {
|
|
@@ -295163,7 +295175,7 @@ var package_default2 = {
|
|
|
295163
295175
|
name: "bsv-mcp",
|
|
295164
295176
|
module: "dist/index.js",
|
|
295165
295177
|
type: "module",
|
|
295166
|
-
version: "0.3.
|
|
295178
|
+
version: "0.3.3",
|
|
295167
295179
|
license: "MIT",
|
|
295168
295180
|
author: "satchmo",
|
|
295169
295181
|
description: "A collection of Bitcoin SV (BSV) tools for the Model Context Protocol (MCP) framework",
|
|
@@ -297158,6 +297170,195 @@ function registerRefreshUtxosTool(server, ctx) {
|
|
|
297158
297170
|
});
|
|
297159
297171
|
}
|
|
297160
297172
|
|
|
297173
|
+
// utils/revealDelegation.ts
|
|
297174
|
+
init_mod2();
|
|
297175
|
+
var publicKey = exports_external.string().regex(/^(02|03)[a-f0-9]{64}$/);
|
|
297176
|
+
var base643 = exports_external.string().min(4).max(16384).regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/);
|
|
297177
|
+
var fields = exports_external.record(exports_external.string().min(1).max(49), base643).refine((v7) => Object.keys(v7).length > 0 && Object.keys(v7).length <= 32);
|
|
297178
|
+
var handoffSchema = exports_external.object({
|
|
297179
|
+
certificate: exports_external.object({
|
|
297180
|
+
type: exports_external.literal("30kchAJIGfLxzCNloCJNLI3AtkgA8UbkxlXU2Cj4PpA="),
|
|
297181
|
+
serialNumber: base643.refine((v7) => Buffer.from(v7, "base64").length === 32),
|
|
297182
|
+
subject: publicKey,
|
|
297183
|
+
certifier: publicKey,
|
|
297184
|
+
revocationOutpoint: exports_external.string().regex(/^[a-f0-9]{64}\.\d{1,10}$/),
|
|
297185
|
+
signature: exports_external.string().regex(/^(?:[a-f0-9]{2}){8,72}$/),
|
|
297186
|
+
fields
|
|
297187
|
+
}).strict(),
|
|
297188
|
+
subjectKeyring: fields,
|
|
297189
|
+
revealTo: publicKey,
|
|
297190
|
+
revelationPath: exports_external.string().max(512)
|
|
297191
|
+
}).strict();
|
|
297192
|
+
|
|
297193
|
+
class RevelationError extends Error {
|
|
297194
|
+
code;
|
|
297195
|
+
status;
|
|
297196
|
+
constructor(code, message, status) {
|
|
297197
|
+
super(message);
|
|
297198
|
+
this.code = code;
|
|
297199
|
+
this.status = status;
|
|
297200
|
+
}
|
|
297201
|
+
}
|
|
297202
|
+
function invalid() {
|
|
297203
|
+
throw new RevelationError("invalid_handoff", "Provide a valid Sigma origin and the owner's BRC-169 handoff.");
|
|
297204
|
+
}
|
|
297205
|
+
function parseHandoff(origin, handoffJSON) {
|
|
297206
|
+
if (handoffJSON.length > 131072 || !/^https?:\/\/[^/?#\\]+\/?$/.test(origin))
|
|
297207
|
+
invalid();
|
|
297208
|
+
const url3 = new URL(origin);
|
|
297209
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url3.hostname);
|
|
297210
|
+
if (url3.username || url3.password || url3.protocol !== "https:" && !(url3.protocol === "http:" && loopback))
|
|
297211
|
+
invalid();
|
|
297212
|
+
const h5 = handoffSchema.parse(JSON.parse(handoffJSON));
|
|
297213
|
+
for (const key of [
|
|
297214
|
+
h5.certificate.subject,
|
|
297215
|
+
h5.certificate.certifier,
|
|
297216
|
+
h5.revealTo
|
|
297217
|
+
]) {
|
|
297218
|
+
if (!PublicKey.fromString(key).validate())
|
|
297219
|
+
invalid();
|
|
297220
|
+
}
|
|
297221
|
+
const match = /^\/api\/agents\/([A-Za-z0-9_-]{1,128})\/delegations\/(.+)\/revelation$/.exec(h5.revelationPath);
|
|
297222
|
+
if (!match || decodeURIComponent(match[2]) !== h5.certificate.serialNumber)
|
|
297223
|
+
invalid();
|
|
297224
|
+
if (Object.keys(h5.subjectKeyring).sort().join(`
|
|
297225
|
+
`) !== Object.keys(h5.certificate.fields).sort().join(`
|
|
297226
|
+
`))
|
|
297227
|
+
invalid();
|
|
297228
|
+
return {
|
|
297229
|
+
h: h5,
|
|
297230
|
+
endpoint: `${url3.origin}/api/agents/${encodeURIComponent(match[1])}/delegations/${encodeURIComponent(h5.certificate.serialNumber)}/revelation`,
|
|
297231
|
+
origin: url3.origin
|
|
297232
|
+
};
|
|
297233
|
+
}
|
|
297234
|
+
async function revealDelegation(wallet5, sigmaOrigin, handoffJSON) {
|
|
297235
|
+
let input;
|
|
297236
|
+
try {
|
|
297237
|
+
input = parseHandoff(sigmaOrigin, handoffJSON);
|
|
297238
|
+
} catch {
|
|
297239
|
+
invalid();
|
|
297240
|
+
}
|
|
297241
|
+
const { h: h5, endpoint, origin } = input;
|
|
297242
|
+
const c6 = h5.certificate;
|
|
297243
|
+
try {
|
|
297244
|
+
const { publicKey: identity6 } = await wallet5.getPublicKey({
|
|
297245
|
+
identityKey: true
|
|
297246
|
+
});
|
|
297247
|
+
if (identity6 !== c6.subject)
|
|
297248
|
+
throw new RevelationError("subject_mismatch", "The connected wallet is not this delegation's subject.");
|
|
297249
|
+
const certificate = new Certificate(c6.type, c6.serialNumber, c6.subject, c6.certifier, c6.revocationOutpoint, c6.fields, c6.signature);
|
|
297250
|
+
if (!await certificate.verify())
|
|
297251
|
+
invalid();
|
|
297252
|
+
} catch (error53) {
|
|
297253
|
+
if (error53 instanceof RevelationError)
|
|
297254
|
+
throw error53;
|
|
297255
|
+
throw new RevelationError("invalid_certificate", "Could not verify the certificate and connected wallet identity.");
|
|
297256
|
+
}
|
|
297257
|
+
let keyring;
|
|
297258
|
+
try {
|
|
297259
|
+
await wallet5.acquireCertificate({
|
|
297260
|
+
...c6,
|
|
297261
|
+
acquisitionProtocol: "direct",
|
|
297262
|
+
keyringRevealer: "certifier",
|
|
297263
|
+
keyringForSubject: h5.subjectKeyring
|
|
297264
|
+
});
|
|
297265
|
+
const proof = await wallet5.proveCertificate({
|
|
297266
|
+
certificate: c6,
|
|
297267
|
+
fieldsToReveal: Object.keys(c6.fields),
|
|
297268
|
+
verifier: h5.revealTo
|
|
297269
|
+
});
|
|
297270
|
+
keyring = fields.parse(proof.keyringForVerifier);
|
|
297271
|
+
} catch {
|
|
297272
|
+
throw new RevelationError("wallet_proof_failed", "The wallet could not acquire or prove this delegation. Check its certificate store before retrying.");
|
|
297273
|
+
}
|
|
297274
|
+
return postRevelation(wallet5, origin, endpoint, keyring);
|
|
297275
|
+
}
|
|
297276
|
+
async function postRevelation(wallet5, origin, endpoint, keyring) {
|
|
297277
|
+
const guarded = new Proxy(wallet5, {
|
|
297278
|
+
get(target, property) {
|
|
297279
|
+
if (property === "createAction" || property === "signAction")
|
|
297280
|
+
return async () => {
|
|
297281
|
+
throw new RevelationError("payment_required", "Sigma revelation must not require a payment.", 402);
|
|
297282
|
+
};
|
|
297283
|
+
const value2 = Reflect.get(target, property, target);
|
|
297284
|
+
return typeof value2 === "function" ? value2.bind(target) : value2;
|
|
297285
|
+
}
|
|
297286
|
+
});
|
|
297287
|
+
let refusal;
|
|
297288
|
+
const guardedFetch = async (url3, init) => {
|
|
297289
|
+
if (String(url3) !== `${origin}/.well-known/auth` && String(url3) !== endpoint) {
|
|
297290
|
+
refusal = new RevelationError("unexpected_endpoint", "Sigma authentication requested an unexpected endpoint.");
|
|
297291
|
+
throw refusal;
|
|
297292
|
+
}
|
|
297293
|
+
const response = await fetch(url3, { ...init, redirect: "error" });
|
|
297294
|
+
if ([401, 402, 403, 409, 422, 429].includes(response.status)) {
|
|
297295
|
+
refusal = new RevelationError(response.status === 402 ? "payment_required" : "request_rejected", "Sigma rejected revelation. Check owner delegation status before retrying.", response.status);
|
|
297296
|
+
throw refusal;
|
|
297297
|
+
}
|
|
297298
|
+
return response;
|
|
297299
|
+
};
|
|
297300
|
+
const transport = new SimplifiedFetchTransport(origin, guardedFetch);
|
|
297301
|
+
const sessions = new SessionManager;
|
|
297302
|
+
const auth5 = new AuthFetch(guarded, undefined, sessions);
|
|
297303
|
+
const peer = new Peer(guarded, transport, undefined, sessions);
|
|
297304
|
+
try {
|
|
297305
|
+
await peer.ready;
|
|
297306
|
+
auth5.peers[origin] = { peer, pendingCertificateRequests: [] };
|
|
297307
|
+
const response = await auth5.fetch(endpoint, {
|
|
297308
|
+
method: "POST",
|
|
297309
|
+
headers: { "Content-Type": "application/json" },
|
|
297310
|
+
body: JSON.stringify({ keyring }),
|
|
297311
|
+
retryCounter: 1,
|
|
297312
|
+
paymentRetryAttempts: 1
|
|
297313
|
+
});
|
|
297314
|
+
if (!response.ok)
|
|
297315
|
+
throw new RevelationError("request_rejected", "Sigma rejected revelation. Check owner delegation status before retrying.", response.status);
|
|
297316
|
+
return { status: "revealed", httpStatus: response.status };
|
|
297317
|
+
} catch (error53) {
|
|
297318
|
+
if (refusal)
|
|
297319
|
+
throw refusal;
|
|
297320
|
+
if (error53 instanceof RevelationError)
|
|
297321
|
+
throw error53;
|
|
297322
|
+
throw new RevelationError("outcome_unknown", "Revelation was not confirmed. Ask the owner to refresh delegation status before retrying; no automatic replay was attempted.");
|
|
297323
|
+
}
|
|
297324
|
+
}
|
|
297325
|
+
|
|
297326
|
+
// tools/wallet/revealDelegation.ts
|
|
297327
|
+
function registerRevealDelegationTool(server, ctx) {
|
|
297328
|
+
server.registerTool("wallet_revealDelegation", {
|
|
297329
|
+
description: "Receive the human owner's BRC-169 certificate handoff, acquire and prove it with this connected agent wallet, and reveal its restrictions to the specified Sigma verifier. Imports a certificate and activates its existing delegation. Never pays, broadcasts, follows redirects, or retries an ambiguous POST. The subject keyring stays with the wallet.",
|
|
297330
|
+
inputSchema: {
|
|
297331
|
+
sigmaOrigin: exports_external.string().max(2048).describe("Explicit Sigma HTTPS origin, or HTTP loopback for local testing"),
|
|
297332
|
+
handoffJSON: exports_external.string().max(131072).describe("JSON package copied by the owner: certificate, subjectKeyring, revealTo, revelationPath")
|
|
297333
|
+
},
|
|
297334
|
+
annotations: {
|
|
297335
|
+
readOnlyHint: false,
|
|
297336
|
+
destructiveHint: false,
|
|
297337
|
+
idempotentHint: false,
|
|
297338
|
+
openWorldHint: true
|
|
297339
|
+
}
|
|
297340
|
+
}, async ({ sigmaOrigin, handoffJSON }) => {
|
|
297341
|
+
try {
|
|
297342
|
+
if (!ctx)
|
|
297343
|
+
throw new RevelationError("wallet_unavailable", "Connect the agent's existing wallet first.");
|
|
297344
|
+
const data = await revealDelegation(ctx.wallet, sigmaOrigin, handoffJSON);
|
|
297345
|
+
return { ...createSuccessResponse(data), structuredContent: data };
|
|
297346
|
+
} catch (error53) {
|
|
297347
|
+
const failure = error53 instanceof RevelationError ? error53 : new RevelationError("revelation_failed", "Delegation revelation failed. Check wallet and owner status before retrying.");
|
|
297348
|
+
const data = {
|
|
297349
|
+
error: failure.code,
|
|
297350
|
+
message: failure.message,
|
|
297351
|
+
...failure.status === undefined ? {} : { status: failure.status }
|
|
297352
|
+
};
|
|
297353
|
+
return {
|
|
297354
|
+
...createSuccessResponse(data),
|
|
297355
|
+
structuredContent: data,
|
|
297356
|
+
isError: true
|
|
297357
|
+
};
|
|
297358
|
+
}
|
|
297359
|
+
});
|
|
297360
|
+
}
|
|
297361
|
+
|
|
297161
297362
|
// tools/wallet/sendAllBsv.ts
|
|
297162
297363
|
var sendAllBsvArgsSchema = exports_external.object({
|
|
297163
297364
|
destination: exports_external.string().describe("Destination P2PKH address to send all funds to")
|
|
@@ -297661,6 +297862,7 @@ function registerWalletTools(server, wallet5, config2) {
|
|
|
297661
297862
|
registerRefreshUtxosTool(server, config2.ctx);
|
|
297662
297863
|
registerWalletGetBalanceTool(server, config2.ctx);
|
|
297663
297864
|
registerBrc100Tools(server, config2.ctx);
|
|
297865
|
+
registerRevealDelegationTool(server, config2.ctx);
|
|
297664
297866
|
if (config2.enableA2bTools && wallet5 && config2.identityPk) {
|
|
297665
297867
|
registerA2bPublishMcpTool(server, wallet5, config2.identityPk, {
|
|
297666
297868
|
disableBroadcasting: config2.disableBroadcasting
|
|
@@ -297846,9 +298048,9 @@ class Wallet {
|
|
|
297846
298048
|
if (!currentPaymentKey) {
|
|
297847
298049
|
throw new Error("No payment key available to derive public key.");
|
|
297848
298050
|
}
|
|
297849
|
-
const
|
|
298051
|
+
const publicKey2 = currentPaymentKey.toPublicKey();
|
|
297850
298052
|
return {
|
|
297851
|
-
publicKey:
|
|
298053
|
+
publicKey: publicKey2.toDER("hex")
|
|
297852
298054
|
};
|
|
297853
298055
|
}
|
|
297854
298056
|
async sendToAddress(address, amountSatoshis) {
|
|
@@ -298089,9 +298291,9 @@ class Wallet2 {
|
|
|
298089
298291
|
if (!currentPaymentKey) {
|
|
298090
298292
|
throw new Error("No payment key available to derive public key.");
|
|
298091
298293
|
}
|
|
298092
|
-
const
|
|
298294
|
+
const publicKey2 = currentPaymentKey.toPublicKey();
|
|
298093
298295
|
return {
|
|
298094
|
-
publicKey:
|
|
298296
|
+
publicKey: publicKey2.toDER("hex")
|
|
298095
298297
|
};
|
|
298096
298298
|
}
|
|
298097
298299
|
async sendToAddress(address, amountSatoshis) {
|
|
@@ -298161,12 +298363,25 @@ init_mod2();
|
|
|
298161
298363
|
function readDroplitSponsorConfig(env = process.env) {
|
|
298162
298364
|
const apiUrl = env.DROPLIT_API_URL;
|
|
298163
298365
|
const faucetName = env.DROPLIT_FAUCET_NAME;
|
|
298164
|
-
|
|
298366
|
+
const siteUrl = env.DROPLIT_SITE_URL;
|
|
298367
|
+
if (apiUrl === undefined && faucetName === undefined && siteUrl === undefined)
|
|
298165
298368
|
return;
|
|
298166
298369
|
if (!apiUrl?.trim() || !faucetName?.trim()) {
|
|
298167
298370
|
throw new Error("DROPLIT_API_URL and DROPLIT_FAUCET_NAME must both be explicitly configured");
|
|
298168
298371
|
}
|
|
298169
|
-
return {
|
|
298372
|
+
return {
|
|
298373
|
+
apiUrl,
|
|
298374
|
+
faucetName,
|
|
298375
|
+
...siteUrl === undefined ? {} : { siteUrl: approvalSiteOrigin2(siteUrl) }
|
|
298376
|
+
};
|
|
298377
|
+
}
|
|
298378
|
+
function approvalSiteOrigin2(value2 = "https://droplit.dev") {
|
|
298379
|
+
const url3 = new URL(value2);
|
|
298380
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url3.hostname);
|
|
298381
|
+
if (url3.username || url3.password || url3.pathname !== "/" || url3.href.includes("?") || url3.href.includes("#") || value2.includes("\\") || !(url3.protocol === "https:" || url3.protocol === "http:" && loopback)) {
|
|
298382
|
+
throw new Error("DROPLIT_SITE_URL requires an HTTPS or HTTP loopback origin without credentials, path, query or fragment");
|
|
298383
|
+
}
|
|
298384
|
+
return url3.origin;
|
|
298170
298385
|
}
|
|
298171
298386
|
|
|
298172
298387
|
class DroplitError2 extends Error {
|
|
@@ -298218,9 +298433,11 @@ function httpFailure2(status, details = {}) {
|
|
|
298218
298433
|
class DroplitClient2 {
|
|
298219
298434
|
config;
|
|
298220
298435
|
authFetch;
|
|
298436
|
+
siteOrigin;
|
|
298221
298437
|
wallet;
|
|
298222
298438
|
constructor(config2) {
|
|
298223
298439
|
this.config = config2;
|
|
298440
|
+
this.siteOrigin = approvalSiteOrigin2(config2.siteUrl);
|
|
298224
298441
|
if (config2.wallet && config2.authKey)
|
|
298225
298442
|
throw new Error("Configure Droplit wallet or authKey, not both");
|
|
298226
298443
|
const url3 = new URL(config2.apiUrl);
|
|
@@ -298252,8 +298469,8 @@ class DroplitClient2 {
|
|
|
298252
298469
|
async getIdentityKey() {
|
|
298253
298470
|
if (!this.wallet)
|
|
298254
298471
|
throw new DroplitError2("authentication_required", "Configure a Droplit wallet identity.");
|
|
298255
|
-
const { publicKey } = await this.wallet.getPublicKey({ identityKey: true });
|
|
298256
|
-
return
|
|
298472
|
+
const { publicKey: publicKey2 } = await this.wallet.getPublicKey({ identityKey: true });
|
|
298473
|
+
return publicKey2;
|
|
298257
298474
|
}
|
|
298258
298475
|
get faucetPath() {
|
|
298259
298476
|
return `/faucet/${encodeURIComponent(this.config.faucetName)}`;
|
|
@@ -298315,6 +298532,7 @@ class DroplitClient2 {
|
|
|
298315
298532
|
throw new DroplitError2("invalid_response", "Sponsor access response did not match the authenticated wallet.");
|
|
298316
298533
|
}
|
|
298317
298534
|
access.approval_path = `/droplit/${encodeURIComponent(this.config.faucetName)}?tab=api&request_key=${encodeURIComponent(identity6)}`;
|
|
298535
|
+
access.approval_url = new URL(access.approval_path, this.siteOrigin).href;
|
|
298318
298536
|
return access;
|
|
298319
298537
|
}
|
|
298320
298538
|
async fund(rawtx) {
|
|
@@ -301203,13 +301421,13 @@ class StorageBunSqlite extends import_StorageProvider.StorageProvider {
|
|
|
301203
301421
|
e2.certificateId = undefined;
|
|
301204
301422
|
if (e2.logger)
|
|
301205
301423
|
e2.logger = undefined;
|
|
301206
|
-
const
|
|
301424
|
+
const fields2 = e2.fields;
|
|
301207
301425
|
if (e2.fields)
|
|
301208
301426
|
e2.fields = undefined;
|
|
301209
301427
|
const id = this.insertRow("certificates", e2);
|
|
301210
301428
|
certificate.certificateId = id;
|
|
301211
|
-
if (
|
|
301212
|
-
for (const field of
|
|
301429
|
+
if (fields2) {
|
|
301430
|
+
for (const field of fields2) {
|
|
301213
301431
|
field.certificateId = id;
|
|
301214
301432
|
field.userId = certificate.userId;
|
|
301215
301433
|
await this.insertCertificateField(field, trx);
|
package/package.json
CHANGED
package/tools/wallet/droplit.ts
CHANGED
|
@@ -53,7 +53,7 @@ export function registerDroplitTools(
|
|
|
53
53
|
error: "approval_required",
|
|
54
54
|
message:
|
|
55
55
|
"Ask the sponsor owner to approve this wallet. Copy the approval URL for manual review; do not auto-open or submit approval.",
|
|
56
|
-
approval_url:
|
|
56
|
+
approval_url: access.approval_url,
|
|
57
57
|
}
|
|
58
58
|
: {}),
|
|
59
59
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { OneSatContext } from "@1sat/actions";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
RevelationError,
|
|
6
|
+
revealDelegation,
|
|
7
|
+
} from "../../utils/revealDelegation";
|
|
8
|
+
import { createSuccessResponse } from "../utils/errorHandler";
|
|
9
|
+
|
|
10
|
+
export function registerRevealDelegationTool(
|
|
11
|
+
server: McpServer,
|
|
12
|
+
ctx?: OneSatContext,
|
|
13
|
+
) {
|
|
14
|
+
server.registerTool(
|
|
15
|
+
"wallet_revealDelegation",
|
|
16
|
+
{
|
|
17
|
+
description:
|
|
18
|
+
"Receive the human owner's BRC-169 certificate handoff, acquire and prove it with this connected agent wallet, and reveal its restrictions to the specified Sigma verifier. Imports a certificate and activates its existing delegation. Never pays, broadcasts, follows redirects, or retries an ambiguous POST. The subject keyring stays with the wallet.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
sigmaOrigin: z
|
|
21
|
+
.string()
|
|
22
|
+
.max(2048)
|
|
23
|
+
.describe(
|
|
24
|
+
"Explicit Sigma HTTPS origin, or HTTP loopback for local testing",
|
|
25
|
+
),
|
|
26
|
+
handoffJSON: z
|
|
27
|
+
.string()
|
|
28
|
+
.max(131072)
|
|
29
|
+
.describe(
|
|
30
|
+
"JSON package copied by the owner: certificate, subjectKeyring, revealTo, revelationPath",
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
annotations: {
|
|
34
|
+
readOnlyHint: false,
|
|
35
|
+
destructiveHint: false,
|
|
36
|
+
idempotentHint: false,
|
|
37
|
+
openWorldHint: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
async ({ sigmaOrigin, handoffJSON }) => {
|
|
41
|
+
try {
|
|
42
|
+
if (!ctx)
|
|
43
|
+
throw new RevelationError(
|
|
44
|
+
"wallet_unavailable",
|
|
45
|
+
"Connect the agent's existing wallet first.",
|
|
46
|
+
);
|
|
47
|
+
const data = await revealDelegation(
|
|
48
|
+
ctx.wallet,
|
|
49
|
+
sigmaOrigin,
|
|
50
|
+
handoffJSON,
|
|
51
|
+
);
|
|
52
|
+
return { ...createSuccessResponse(data), structuredContent: data };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const failure =
|
|
55
|
+
error instanceof RevelationError
|
|
56
|
+
? error
|
|
57
|
+
: new RevelationError(
|
|
58
|
+
"revelation_failed",
|
|
59
|
+
"Delegation revelation failed. Check wallet and owner status before retrying.",
|
|
60
|
+
);
|
|
61
|
+
const data = {
|
|
62
|
+
error: failure.code,
|
|
63
|
+
message: failure.message,
|
|
64
|
+
...(failure.status === undefined ? {} : { status: failure.status }),
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
...createSuccessResponse(data),
|
|
68
|
+
structuredContent: data,
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
}
|
package/tools/wallet/tools.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { registerOpnsDeregisterTool } from "./opnsDeregister";
|
|
|
19
19
|
import { registerOpnsRegisterTool } from "./opnsRegister";
|
|
20
20
|
import { registerPurchaseListingTool } from "./purchaseListing";
|
|
21
21
|
import { registerRefreshUtxosTool } from "./refreshUtxos";
|
|
22
|
+
import { registerRevealDelegationTool } from "./revealDelegation";
|
|
22
23
|
import { registerSendAllBsvTool } from "./sendAllBsv";
|
|
23
24
|
import { registerSendBsvTool } from "./sendBsv";
|
|
24
25
|
import { registerSignBsmTool } from "./signBsm";
|
|
@@ -58,6 +59,7 @@ export function registerWalletTools(
|
|
|
58
59
|
|
|
59
60
|
// Register full BRC-100 wallet interface
|
|
60
61
|
registerBrc100Tools(server, config.ctx);
|
|
62
|
+
registerRevealDelegationTool(server, config.ctx);
|
|
61
63
|
|
|
62
64
|
// A2B tools have to be explicitly enabled
|
|
63
65
|
if (config.enableA2bTools && wallet && config.identityPk) {
|