agentic-wallet-mcp 0.14.0 → 0.14.2
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 +26 -0
- package/README.md +6 -6
- package/dist/server-bundle.cjs +618 -121
- package/package.json +1 -1
package/dist/server-bundle.cjs
CHANGED
|
@@ -2982,7 +2982,7 @@ var require_compile = __commonJS({
|
|
|
2982
2982
|
const schOrFunc = root.refs[ref];
|
|
2983
2983
|
if (schOrFunc)
|
|
2984
2984
|
return schOrFunc;
|
|
2985
|
-
let _sch =
|
|
2985
|
+
let _sch = resolve2.call(this, root, ref);
|
|
2986
2986
|
if (_sch === void 0) {
|
|
2987
2987
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
2988
2988
|
const { schemaId } = this.opts;
|
|
@@ -3009,7 +3009,7 @@ var require_compile = __commonJS({
|
|
|
3009
3009
|
function sameSchemaEnv(s1, s2) {
|
|
3010
3010
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3011
3011
|
}
|
|
3012
|
-
function
|
|
3012
|
+
function resolve2(root, ref) {
|
|
3013
3013
|
let sch;
|
|
3014
3014
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3015
3015
|
ref = sch;
|
|
@@ -3640,7 +3640,7 @@ var require_fast_uri = __commonJS({
|
|
|
3640
3640
|
}
|
|
3641
3641
|
return uri;
|
|
3642
3642
|
}
|
|
3643
|
-
function
|
|
3643
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
3644
3644
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3645
3645
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
3646
3646
|
schemelessOptions.skipEscape = true;
|
|
@@ -3898,7 +3898,7 @@ var require_fast_uri = __commonJS({
|
|
|
3898
3898
|
var fastUri = {
|
|
3899
3899
|
SCHEMES,
|
|
3900
3900
|
normalize: normalize3,
|
|
3901
|
-
resolve,
|
|
3901
|
+
resolve: resolve2,
|
|
3902
3902
|
resolveComponent,
|
|
3903
3903
|
equal,
|
|
3904
3904
|
serialize,
|
|
@@ -7030,7 +7030,7 @@ var require_aspromise = __commonJS({
|
|
|
7030
7030
|
var params = new Array(arguments.length - 1), offset = 0, index = 2, pending = true;
|
|
7031
7031
|
while (index < arguments.length)
|
|
7032
7032
|
params[offset++] = arguments[index++];
|
|
7033
|
-
return new Promise(function executor(
|
|
7033
|
+
return new Promise(function executor(resolve2, reject) {
|
|
7034
7034
|
params[offset] = function callback(err) {
|
|
7035
7035
|
if (pending) {
|
|
7036
7036
|
pending = false;
|
|
@@ -7040,7 +7040,7 @@ var require_aspromise = __commonJS({
|
|
|
7040
7040
|
var params2 = new Array(arguments.length - 1), offset2 = 0;
|
|
7041
7041
|
while (offset2 < params2.length)
|
|
7042
7042
|
params2[offset2++] = arguments[offset2];
|
|
7043
|
-
|
|
7043
|
+
resolve2.apply(null, params2);
|
|
7044
7044
|
}
|
|
7045
7045
|
}
|
|
7046
7046
|
};
|
|
@@ -8568,7 +8568,7 @@ var require_path = __commonJS({
|
|
|
8568
8568
|
return prefix + parts.join("/");
|
|
8569
8569
|
}
|
|
8570
8570
|
);
|
|
8571
|
-
path.resolve = function
|
|
8571
|
+
path.resolve = function resolve2(originPath, includePath, alreadyNormalized) {
|
|
8572
8572
|
if (!alreadyNormalized)
|
|
8573
8573
|
includePath = normalize3(includePath);
|
|
8574
8574
|
if (isAbsolute(includePath))
|
|
@@ -8848,7 +8848,7 @@ var require_field = __commonJS({
|
|
|
8848
8848
|
keepComments ? this.comment : void 0
|
|
8849
8849
|
]);
|
|
8850
8850
|
};
|
|
8851
|
-
Field.prototype.resolve = function
|
|
8851
|
+
Field.prototype.resolve = function resolve2() {
|
|
8852
8852
|
if (this.resolved)
|
|
8853
8853
|
return this;
|
|
8854
8854
|
if ((this.typeDefault = types.defaults[this.type]) === void 0) {
|
|
@@ -9252,7 +9252,7 @@ var require_mapfield = __commonJS({
|
|
|
9252
9252
|
keepComments ? this.comment : void 0
|
|
9253
9253
|
]);
|
|
9254
9254
|
};
|
|
9255
|
-
MapField.prototype.resolve = function
|
|
9255
|
+
MapField.prototype.resolve = function resolve2() {
|
|
9256
9256
|
if (this.resolved)
|
|
9257
9257
|
return this;
|
|
9258
9258
|
if (types.mapKey[this.keyType] === void 0)
|
|
@@ -9329,7 +9329,7 @@ var require_method = __commonJS({
|
|
|
9329
9329
|
this.parsedOptions
|
|
9330
9330
|
]);
|
|
9331
9331
|
};
|
|
9332
|
-
Method.prototype.resolve = function
|
|
9332
|
+
Method.prototype.resolve = function resolve2() {
|
|
9333
9333
|
if (this.resolved)
|
|
9334
9334
|
return this;
|
|
9335
9335
|
this.resolvedRequestType = this.parent.lookupType(this.requestType);
|
|
@@ -10610,7 +10610,7 @@ var require_object = __commonJS({
|
|
|
10610
10610
|
this.parent = null;
|
|
10611
10611
|
this.resolved = false;
|
|
10612
10612
|
};
|
|
10613
|
-
ReflectionObject.prototype.resolve = function
|
|
10613
|
+
ReflectionObject.prototype.resolve = function resolve2() {
|
|
10614
10614
|
if (this.resolved)
|
|
10615
10615
|
return this;
|
|
10616
10616
|
if (this.root instanceof Root)
|
|
@@ -20049,7 +20049,7 @@ var require_sha3 = __commonJS({
|
|
|
20049
20049
|
function _interopRequireDefault(obj) {
|
|
20050
20050
|
return obj && obj.__esModule ? obj : { "default": obj };
|
|
20051
20051
|
}
|
|
20052
|
-
var
|
|
20052
|
+
var createHash12 = function createHash13(_ref) {
|
|
20053
20053
|
var allowedSizes = _ref.allowedSizes, defaultSize = _ref.defaultSize, padding = _ref.padding;
|
|
20054
20054
|
return function Hash() {
|
|
20055
20055
|
var _this = this;
|
|
@@ -20088,11 +20088,11 @@ var require_sha3 = __commonJS({
|
|
|
20088
20088
|
return this;
|
|
20089
20089
|
};
|
|
20090
20090
|
};
|
|
20091
|
-
var Keccak =
|
|
20091
|
+
var Keccak = createHash12({ allowedSizes: [224, 256, 384, 512], defaultSize: 512, padding: 1 });
|
|
20092
20092
|
exports2.Keccak = Keccak;
|
|
20093
|
-
var SHA3 =
|
|
20093
|
+
var SHA3 = createHash12({ allowedSizes: [224, 256, 384, 512], defaultSize: 512, padding: 6 });
|
|
20094
20094
|
exports2.SHA3 = SHA3;
|
|
20095
|
-
var SHAKE =
|
|
20095
|
+
var SHAKE = createHash12({ allowedSizes: [128, 256], defaultSize: 256, padding: 31 });
|
|
20096
20096
|
exports2.SHAKE = SHAKE;
|
|
20097
20097
|
var SHA3Hash = Keccak;
|
|
20098
20098
|
exports2.SHA3Hash = SHA3Hash;
|
|
@@ -31382,16 +31382,16 @@ __export(index_exports, {
|
|
|
31382
31382
|
describeAssetAmount: () => describeAssetAmount
|
|
31383
31383
|
});
|
|
31384
31384
|
module.exports = __toCommonJS(index_exports);
|
|
31385
|
-
var
|
|
31385
|
+
var import_node_crypto13 = require("node:crypto");
|
|
31386
31386
|
var import_node_fs = require("node:fs");
|
|
31387
|
-
var
|
|
31387
|
+
var import_promises10 = require("node:fs/promises");
|
|
31388
31388
|
var import_node_os2 = require("node:os");
|
|
31389
|
-
var
|
|
31389
|
+
var import_node_path10 = require("node:path");
|
|
31390
31390
|
|
|
31391
31391
|
// package.json
|
|
31392
31392
|
var package_default = {
|
|
31393
31393
|
name: "agentic-wallet-mcp",
|
|
31394
|
-
version: "0.14.
|
|
31394
|
+
version: "0.14.2",
|
|
31395
31395
|
description: "Agent-facing MCP wallet for Zetrix \u2014 orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
|
|
31396
31396
|
keywords: [
|
|
31397
31397
|
"mcp",
|
|
@@ -38773,7 +38773,7 @@ var Protocol = class {
|
|
|
38773
38773
|
return;
|
|
38774
38774
|
}
|
|
38775
38775
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
38776
|
-
await new Promise((
|
|
38776
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
38777
38777
|
options?.signal?.throwIfAborted();
|
|
38778
38778
|
}
|
|
38779
38779
|
} catch (error2) {
|
|
@@ -38790,7 +38790,7 @@ var Protocol = class {
|
|
|
38790
38790
|
*/
|
|
38791
38791
|
request(request, resultSchema, options) {
|
|
38792
38792
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
38793
|
-
return new Promise((
|
|
38793
|
+
return new Promise((resolve2, reject) => {
|
|
38794
38794
|
const earlyReject = (error2) => {
|
|
38795
38795
|
reject(error2);
|
|
38796
38796
|
};
|
|
@@ -38868,7 +38868,7 @@ var Protocol = class {
|
|
|
38868
38868
|
if (!parseResult.success) {
|
|
38869
38869
|
reject(parseResult.error);
|
|
38870
38870
|
} else {
|
|
38871
|
-
|
|
38871
|
+
resolve2(parseResult.data);
|
|
38872
38872
|
}
|
|
38873
38873
|
} catch (error2) {
|
|
38874
38874
|
reject(error2);
|
|
@@ -39129,12 +39129,12 @@ var Protocol = class {
|
|
|
39129
39129
|
}
|
|
39130
39130
|
} catch {
|
|
39131
39131
|
}
|
|
39132
|
-
return new Promise((
|
|
39132
|
+
return new Promise((resolve2, reject) => {
|
|
39133
39133
|
if (signal.aborted) {
|
|
39134
39134
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
39135
39135
|
return;
|
|
39136
39136
|
}
|
|
39137
|
-
const timeoutId = setTimeout(
|
|
39137
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
39138
39138
|
signal.addEventListener("abort", () => {
|
|
39139
39139
|
clearTimeout(timeoutId);
|
|
39140
39140
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -40004,12 +40004,12 @@ var StdioServerTransport = class {
|
|
|
40004
40004
|
this.onclose?.();
|
|
40005
40005
|
}
|
|
40006
40006
|
send(message) {
|
|
40007
|
-
return new Promise((
|
|
40007
|
+
return new Promise((resolve2) => {
|
|
40008
40008
|
const json = serializeMessage(message);
|
|
40009
40009
|
if (this._stdout.write(json)) {
|
|
40010
|
-
|
|
40010
|
+
resolve2();
|
|
40011
40011
|
} else {
|
|
40012
|
-
this._stdout.once("drain",
|
|
40012
|
+
this._stdout.once("drain", resolve2);
|
|
40013
40013
|
}
|
|
40014
40014
|
});
|
|
40015
40015
|
}
|
|
@@ -40860,10 +40860,10 @@ async function fetchTemplateFields(templateId, registryAddress, nodeBaseUrl, que
|
|
|
40860
40860
|
var WalletBeError = class extends Error {
|
|
40861
40861
|
errorCode;
|
|
40862
40862
|
cause;
|
|
40863
|
-
constructor(message,
|
|
40863
|
+
constructor(message, errorCode2, cause) {
|
|
40864
40864
|
super(message);
|
|
40865
40865
|
this.name = "WalletBeError";
|
|
40866
|
-
this.errorCode =
|
|
40866
|
+
this.errorCode = errorCode2;
|
|
40867
40867
|
this.cause = cause;
|
|
40868
40868
|
}
|
|
40869
40869
|
};
|
|
@@ -41305,6 +41305,15 @@ async function resolveIssuerProofKeys(vc, resolver) {
|
|
|
41305
41305
|
return { bbsPublicKey, ed25519PublicKey };
|
|
41306
41306
|
}
|
|
41307
41307
|
|
|
41308
|
+
// src/orchestrator/birthcert-choice.ts
|
|
41309
|
+
function aiBirthcertChoice(kind, opts = {}) {
|
|
41310
|
+
const note = `AI Birthcert comes in two kinds. BASIC: self-declared by the agent, with no check of the owner's identity; it is quick, shows only the agent's username, and does not mean the owner was verified. VERIFIED: the owner's identity is confirmed through MyDigital ID; the fee is paid up front, the owner completes a check in the MyID app, and the agent name must be unique (the name is only checked after payment, so a name that turns out to be taken is refused late, and whether the fee was spent cannot be confirmed). Each kind has its own price, read from the check for that kind: never assume Basic is free. This result is for the ${kind === "basic" ? "BASIC" : "VERIFIED"} one. If the user has not said which kind they want (they did not say "verified" or "basic", and did not mention MyDigital ID or owner verification), do not start either: run this check for both kinds (it is free) to see what the wallet already holds, tell the user the difference in plain words, offer only the kind or kinds they do not already hold, and ask which they prefer. If they want both, issue them one after the other, Basic first, and finish and show the first before starting the second; each has its own price and needs its own yes, so "both" is not a yes to either payment. Holding one kind never blocks the other, and replacing one that is held is its own explicit choice.`;
|
|
41311
|
+
return opts.unavailable ? note + " The Verified kind is not available on this wallet or network: say so, and offer the Basic one only." : note;
|
|
41312
|
+
}
|
|
41313
|
+
|
|
41314
|
+
// src/mcp-tools.ts
|
|
41315
|
+
var import_promises4 = require("node:fs/promises");
|
|
41316
|
+
|
|
41308
41317
|
// src/orchestrator/prove.ts
|
|
41309
41318
|
async function proveIdentity(wallet, proofRequestHeader, holderDid) {
|
|
41310
41319
|
const pr = await wallet.respondToChallenge(proofRequestHeader, holderDid);
|
|
@@ -41615,7 +41624,7 @@ var RECOVERY_MAX_POLLS = 5;
|
|
|
41615
41624
|
var RECOVERY_POLL_DELAY_MS = 2e3;
|
|
41616
41625
|
async function pollSettlementOutcome(deps, paymentId) {
|
|
41617
41626
|
if (!deps.mbi.getStatus) return void 0;
|
|
41618
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((
|
|
41627
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
41619
41628
|
let last;
|
|
41620
41629
|
for (let polls = 1; polls <= RECOVERY_MAX_POLLS; polls++) {
|
|
41621
41630
|
let status;
|
|
@@ -42236,7 +42245,7 @@ var TEMPLATE_ALIASES = [
|
|
|
42236
42245
|
deriveAttributes: { id: "agentUsername" },
|
|
42237
42246
|
validateAttributes: { dob: validateDob, countryOfOrigin: validateCountryOfOrigin },
|
|
42238
42247
|
passDesignId: {
|
|
42239
|
-
testnet: "did:zid:
|
|
42248
|
+
testnet: "did:zid:bb7202482ef6053be6d46ad8e2190b0ba1e3b2024d24ce7f37bc4c3747e2378f",
|
|
42240
42249
|
mainnet: "did:zid:915955cb71c6fd2a256d04344f57381084903cb6a85a1c9ddb71c746f08932ab"
|
|
42241
42250
|
}
|
|
42242
42251
|
}
|
|
@@ -42301,6 +42310,8 @@ async function credentialPreflight(deps, input) {
|
|
|
42301
42310
|
const blockers = [];
|
|
42302
42311
|
const notChecked = [];
|
|
42303
42312
|
const isVerified = input.credential === VERIFIED_AI_BIRTHCERT;
|
|
42313
|
+
const kind = isVerified ? "verified" : deps.isBasicAiBirthcert?.(input.credential) ? "basic" : void 0;
|
|
42314
|
+
const choice = kind ? { choice: aiBirthcertChoice(kind) } : {};
|
|
42304
42315
|
let alreadyHeld;
|
|
42305
42316
|
if (deps.findHeld) {
|
|
42306
42317
|
try {
|
|
@@ -42318,7 +42329,8 @@ async function credentialPreflight(deps, input) {
|
|
|
42318
42329
|
blockers: [
|
|
42319
42330
|
`This wallet already holds a valid ${found.label}${until}${found.vcId ? `, ${found.vcId}` : ""}. Tell the user, and do not start another: a new one would REPLACE it (the wallet keeps one per template) and costs the fee. Only if the user explicitly says they want a replacement, run this check again with replacing: true.`
|
|
42320
42331
|
],
|
|
42321
|
-
notChecked: []
|
|
42332
|
+
notChecked: [],
|
|
42333
|
+
...choice
|
|
42322
42334
|
};
|
|
42323
42335
|
}
|
|
42324
42336
|
} else {
|
|
@@ -42344,7 +42356,8 @@ async function credentialPreflight(deps, input) {
|
|
|
42344
42356
|
...alreadyHeld ? { alreadyHeld } : {},
|
|
42345
42357
|
...schema ? { schema } : {},
|
|
42346
42358
|
blockers: [`Could not price this credential: ${why}`],
|
|
42347
|
-
notChecked: [...notChecked, ...uncheckable(isVerified)]
|
|
42359
|
+
notChecked: [...notChecked, ...uncheckable(isVerified)],
|
|
42360
|
+
...choice
|
|
42348
42361
|
};
|
|
42349
42362
|
}
|
|
42350
42363
|
const asset = String(quote.asset ?? "");
|
|
@@ -42360,12 +42373,21 @@ async function credentialPreflight(deps, input) {
|
|
|
42360
42373
|
} else if (!isFree && BigInt(feeBalance.balance) < BigInt(requiredRaw)) {
|
|
42361
42374
|
blockers.push(`Not enough ${feeBalance.token}: the fee is ${renderAmount(requiredRaw, feeBalance)}, the balance is ${feeBalance.display}.`);
|
|
42362
42375
|
}
|
|
42376
|
+
const gasNotes = [];
|
|
42363
42377
|
if (selfPaysGas && asset !== NATIVE) {
|
|
42364
42378
|
const gas = balances[1];
|
|
42365
42379
|
if (gas && "error" in gas) {
|
|
42366
|
-
|
|
42380
|
+
const msg = `Could not read the ZTX balance (${gas.error}) \u2014 ZTX pays network gas and is separate from the fee.`;
|
|
42381
|
+
if (isFree) gasNotes.push(`${msg} Issuance is free right now, so none is needed unless it becomes chargeable.`);
|
|
42382
|
+
else blockers.push(msg);
|
|
42367
42383
|
} else if (gas && BigInt(gas.balance) === 0n) {
|
|
42368
|
-
|
|
42384
|
+
if (isFree) {
|
|
42385
|
+
gasNotes.push(
|
|
42386
|
+
"This wallet holds no ZTX. Issuance is free right now, so no payment is made and no gas is needed; if it becomes chargeable and this wallet pays its own gas, it will need ZTX."
|
|
42387
|
+
);
|
|
42388
|
+
} else {
|
|
42389
|
+
blockers.push(`No ZTX for network gas. ZTX is separate from the ${feeBalance.token ?? asset} fee and is needed for every transaction.`);
|
|
42390
|
+
}
|
|
42369
42391
|
}
|
|
42370
42392
|
}
|
|
42371
42393
|
const cap = deps.policyGoverns && await deps.policyGoverns(asset) ? describePolicyGovernedCap(asset) : describePaymentCap(asset, requiredRaw, deps.caps);
|
|
@@ -42386,7 +42408,8 @@ async function credentialPreflight(deps, input) {
|
|
|
42386
42408
|
...alreadyHeld ? { alreadyHeld } : {},
|
|
42387
42409
|
...schema ? { schema } : {},
|
|
42388
42410
|
blockers,
|
|
42389
|
-
notChecked: [...notChecked, ...uncheckable(isVerified, isFree)]
|
|
42411
|
+
notChecked: [...notChecked, ...gasNotes, ...uncheckable(isVerified, isFree)],
|
|
42412
|
+
...choice
|
|
42390
42413
|
};
|
|
42391
42414
|
}
|
|
42392
42415
|
function renderAmount(raw, balance) {
|
|
@@ -42416,7 +42439,7 @@ function uncheckable(isVerified, isFree = false) {
|
|
|
42416
42439
|
}
|
|
42417
42440
|
if (isFree) {
|
|
42418
42441
|
items.unshift(
|
|
42419
|
-
"Whether issuance is still free at apply time \u2014
|
|
42442
|
+
"Whether issuance is still free at apply time \u2014 whether the service charges is a service-wide setting and can change between this quote and issuance."
|
|
42420
42443
|
);
|
|
42421
42444
|
}
|
|
42422
42445
|
return items;
|
|
@@ -43962,7 +43985,7 @@ async function attachAttributeMeanings(result, readVocabulary) {
|
|
|
43962
43985
|
}
|
|
43963
43986
|
|
|
43964
43987
|
// src/orchestrator/write-policy.ts
|
|
43965
|
-
var
|
|
43988
|
+
var import_node_crypto7 = require("node:crypto");
|
|
43966
43989
|
|
|
43967
43990
|
// src/clients/policy-write-receipt-store.ts
|
|
43968
43991
|
var import_node_crypto5 = require("node:crypto");
|
|
@@ -44052,6 +44075,12 @@ function parseGasPayer(value) {
|
|
|
44052
44075
|
if (value === "self" || value === "sponsored") return { ok: true, gasPayer: value };
|
|
44053
44076
|
return { ok: false };
|
|
44054
44077
|
}
|
|
44078
|
+
function parseMaxGasRaw(value) {
|
|
44079
|
+
if (value === void 0 || value === null || value === "") return { ok: true };
|
|
44080
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return { ok: true, maxGasRaw: String(value) };
|
|
44081
|
+
if (typeof value === "string" && /^[1-9]\d{0,17}$/.test(value)) return { ok: true, maxGasRaw: value };
|
|
44082
|
+
return { ok: false };
|
|
44083
|
+
}
|
|
44055
44084
|
var UINT = /^\d+$/;
|
|
44056
44085
|
var RESERVE_DIVISOR = 5n;
|
|
44057
44086
|
var MAX_REASON = 200;
|
|
@@ -44124,6 +44153,7 @@ async function assessSelfPay(deps, call) {
|
|
|
44124
44153
|
}
|
|
44125
44154
|
|
|
44126
44155
|
// src/orchestrator/policy-self-pay.ts
|
|
44156
|
+
var import_node_crypto6 = require("node:crypto");
|
|
44127
44157
|
var SCALAR_OF = {
|
|
44128
44158
|
STRING: "STRING",
|
|
44129
44159
|
NUMBER: "NUMBER",
|
|
@@ -44132,6 +44162,7 @@ var SCALAR_OF = {
|
|
|
44132
44162
|
NUMBER_LIST: "NUMBER",
|
|
44133
44163
|
ADDRESS_LIST: "ADDRESS"
|
|
44134
44164
|
};
|
|
44165
|
+
var SELF_PAY_CEIL_LEDGERS = 60;
|
|
44135
44166
|
var MAX_ATTRIBUTES = 64;
|
|
44136
44167
|
var MAX_LIST_ENTRIES = 500;
|
|
44137
44168
|
function parseList2(text) {
|
|
@@ -44225,6 +44256,9 @@ function createSelfPay(chain) {
|
|
|
44225
44256
|
},
|
|
44226
44257
|
queryNativeBalance: chain.queryNativeBalance,
|
|
44227
44258
|
...chain.getTransaction ? { lookupTransaction: chain.getTransaction } : {},
|
|
44259
|
+
pendingSend: (policyKey) => chain.markers.get(chain.ownerAddress, policyKey),
|
|
44260
|
+
clearSend: (policyKey, txHash) => chain.markers.remove(chain.ownerAddress, policyKey, txHash),
|
|
44261
|
+
currentLedger: chain.currentLedger,
|
|
44228
44262
|
async execute(call, limit) {
|
|
44229
44263
|
const built = buildPolicyCall(call);
|
|
44230
44264
|
if (!built.ok) return { kind: "rejected", reason: built.reason };
|
|
@@ -44250,9 +44284,17 @@ function createSelfPay(chain) {
|
|
|
44250
44284
|
reason: `the node now estimates a fee of ${fee.feeLimit}, above the ${limit.maxFeeRaw} this wallet checked its balance against`
|
|
44251
44285
|
};
|
|
44252
44286
|
}
|
|
44287
|
+
let ceil;
|
|
44288
|
+
try {
|
|
44289
|
+
const ledger = await chain.currentLedger();
|
|
44290
|
+
if (typeof ledger !== "string" || !/^\d{1,18}$/.test(ledger)) throw new Error("the ledger number is not available");
|
|
44291
|
+
ceil = (BigInt(ledger) + BigInt(SELF_PAY_CEIL_LEDGERS)).toString();
|
|
44292
|
+
} catch (e) {
|
|
44293
|
+
return { kind: "rejected", reason: `the ledger number could not be read, so the transaction could not be given an expiry: ${boundedReason(e)}` };
|
|
44294
|
+
}
|
|
44253
44295
|
let blob;
|
|
44254
44296
|
try {
|
|
44255
|
-
blob = chain.buildBlob({ sourceAddress: chain.ownerAddress, nonce, gasPrice: fee.gasPrice, feeLimit: ceiling.toString(), operations: [operation] }).blob;
|
|
44297
|
+
blob = chain.buildBlob({ sourceAddress: chain.ownerAddress, nonce, gasPrice: fee.gasPrice, feeLimit: ceiling.toString(), ceilLedgerSeq: ceil, operations: [operation] }).blob;
|
|
44256
44298
|
} catch (e) {
|
|
44257
44299
|
return { kind: "rejected", reason: `the transaction could not be built: ${boundedReason(e)}` };
|
|
44258
44300
|
}
|
|
@@ -44264,12 +44306,37 @@ function createSelfPay(chain) {
|
|
|
44264
44306
|
if (refusal) return { kind: "refused", refusal };
|
|
44265
44307
|
return { kind: "sign_failed", reason: boundedReason(e) };
|
|
44266
44308
|
}
|
|
44309
|
+
const txHash = (0, import_node_crypto6.createHash)("sha256").update(Buffer.from(blob, "hex")).digest("hex");
|
|
44310
|
+
const marker = {
|
|
44311
|
+
policyKey: call.policyKey,
|
|
44312
|
+
ownerAddress: chain.ownerAddress,
|
|
44313
|
+
operation: call.operation,
|
|
44314
|
+
nonce,
|
|
44315
|
+
txHash,
|
|
44316
|
+
ceilLedgerSeq: ceil,
|
|
44317
|
+
submittedAt: (chain.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
44318
|
+
};
|
|
44319
|
+
try {
|
|
44320
|
+
await chain.markers.set(marker);
|
|
44321
|
+
} catch (e) {
|
|
44322
|
+
try {
|
|
44323
|
+
await chain.markers.remove(chain.ownerAddress, call.policyKey, txHash);
|
|
44324
|
+
} catch {
|
|
44325
|
+
}
|
|
44326
|
+
return { kind: "rejected", reason: `the send could not be recorded, so nothing was sent: ${boundedReason(e)}` };
|
|
44327
|
+
}
|
|
44267
44328
|
try {
|
|
44268
44329
|
const { hash } = await chain.submit({ blob, signBlob: signed.signBlob, publicKey: signed.publicKey });
|
|
44269
|
-
return { kind: "submitted", txHash: hash, nonce, feeLimit: ceiling.toString() };
|
|
44330
|
+
return { kind: "submitted", txHash: hash, nonce, feeLimit: ceiling.toString(), ceilLedgerSeq: ceil };
|
|
44270
44331
|
} catch (e) {
|
|
44271
|
-
if (isSubmitRejection(e))
|
|
44272
|
-
|
|
44332
|
+
if (isSubmitRejection(e)) {
|
|
44333
|
+
try {
|
|
44334
|
+
await chain.markers.remove(chain.ownerAddress, call.policyKey, txHash);
|
|
44335
|
+
} catch {
|
|
44336
|
+
}
|
|
44337
|
+
return { kind: "rejected", reason: `the node rejected the transaction: ${boundedReason(e)}` };
|
|
44338
|
+
}
|
|
44339
|
+
return { kind: "unknown", reason: boundedReason(e), nonce, txHash, ceilLedgerSeq: ceil };
|
|
44273
44340
|
}
|
|
44274
44341
|
}
|
|
44275
44342
|
};
|
|
@@ -44530,6 +44597,8 @@ function isDryRun(value) {
|
|
|
44530
44597
|
async function writePolicy(deps, input) {
|
|
44531
44598
|
const gas = parseGasPayer(input?.gasPayer);
|
|
44532
44599
|
if (!gas.ok) return refusedGasPayer(input?.policyKey);
|
|
44600
|
+
const maxGas = parseMaxGasRaw(input?.maxGasRaw);
|
|
44601
|
+
if (!maxGas.ok) return refusedMaxGas(input?.policyKey);
|
|
44533
44602
|
if (!deps.client && gas.gasPayer !== "self" && !(gas.gasPayer === void 0 && deps.selfPay)) {
|
|
44534
44603
|
return unavailable(
|
|
44535
44604
|
`No policy write service is configured for ${deps.network ?? "this network"}, so nothing was attempted and nothing was paid.`
|
|
@@ -44561,7 +44630,7 @@ async function writePolicy(deps, input) {
|
|
|
44561
44630
|
...input.validToBlock !== void 0 ? { validToBlock: input.validToBlock } : {},
|
|
44562
44631
|
// A sha256 hex string: the service's column is 128 characters, and the old `${owner}-${policyKey}-${ISO}` overflowed it
|
|
44563
44632
|
// for a policy key longer than about 65. Still unique per owner, key and moment.
|
|
44564
|
-
requestKey: input.requestKey ?? (0,
|
|
44633
|
+
requestKey: input.requestKey ?? (0, import_node_crypto7.createHash)("sha256").update(`${deps.ownerAddress}-${input.policyKey}-${(deps.now?.() ?? /* @__PURE__ */ new Date()).toISOString()}`).digest("hex")
|
|
44565
44634
|
};
|
|
44566
44635
|
const supplied = input.templateContractAddress;
|
|
44567
44636
|
const omitted = supplied === void 0 || supplied === null || typeof supplied === "string" && supplied.trim() === "";
|
|
@@ -44616,7 +44685,8 @@ async function writePolicy(deps, input) {
|
|
|
44616
44685
|
pollBudgetMs: input.pollBudgetMs ?? DEFAULT_BUDGET_MS,
|
|
44617
44686
|
tool: "write_policy",
|
|
44618
44687
|
verb: "write",
|
|
44619
|
-
...selfUnavailable ? { unavailable: selfUnavailable } : {}
|
|
44688
|
+
...selfUnavailable ? { unavailable: selfUnavailable } : {},
|
|
44689
|
+
...maxGas.maxGasRaw ? { maxGasRaw: maxGas.maxGasRaw } : {}
|
|
44620
44690
|
});
|
|
44621
44691
|
}
|
|
44622
44692
|
if (!deps.client) {
|
|
@@ -44681,7 +44751,7 @@ async function writePolicy(deps, input) {
|
|
|
44681
44751
|
...gas.gasPayer ? { gasPayer: gas.gasPayer } : {}
|
|
44682
44752
|
});
|
|
44683
44753
|
}
|
|
44684
|
-
async function
|
|
44754
|
+
async function recoverInFlightLocked(deps, client, a) {
|
|
44685
44755
|
const { pre } = a;
|
|
44686
44756
|
const known = await deps.receipts.get(pre.blobId);
|
|
44687
44757
|
const receipt = {
|
|
@@ -44712,6 +44782,8 @@ async function recoverInFlight(deps, client, a) {
|
|
|
44712
44782
|
message: `${a.dryRun ? "DRY RUN" : "NOT CONFIRMED"} \u2014 nothing was collected. A write under "${a.policyKey}" has ALREADY BEEN PAID FOR and is waiting to be collected; the service said: "${clip2(pre.detail)}". Pass the receipt below to check_policy_write to finish it. Do not pay again.`
|
|
44713
44783
|
};
|
|
44714
44784
|
}
|
|
44785
|
+
const heldCollect = await holdForSelfPaySend(deps, a.policyKey, COLLECT_HELD);
|
|
44786
|
+
if (heldCollect) return { ...heldCollect, paid: true, paymentReceipt: pre.blobId, policyKey: a.policyKey, interpretation: a.interpretation };
|
|
44715
44787
|
const stored = known ? operationOf(known) : void 0;
|
|
44716
44788
|
if (stored !== void 0 && pre.operation !== void 0 && pre.operation !== stored) {
|
|
44717
44789
|
return {
|
|
@@ -44771,6 +44843,13 @@ async function describeQuote(deps, accept) {
|
|
|
44771
44843
|
}
|
|
44772
44844
|
return { asset, amount, extra, amountHuman, affordability };
|
|
44773
44845
|
}
|
|
44846
|
+
function refusedMaxGas(policyKey) {
|
|
44847
|
+
return {
|
|
44848
|
+
state: "refused",
|
|
44849
|
+
...typeof policyKey === "string" ? { policyKey } : {},
|
|
44850
|
+
message: "maxGasRaw must be a positive whole number of raw units (micro-ZTX): the requiredRaw of the self option the user was shown, or be left out. NOTHING WAS PAID."
|
|
44851
|
+
};
|
|
44852
|
+
}
|
|
44774
44853
|
function refusedGasPayer(policyKey) {
|
|
44775
44854
|
return {
|
|
44776
44855
|
state: "refused",
|
|
@@ -44799,7 +44878,7 @@ function selfPayNotAvailable(a) {
|
|
|
44799
44878
|
function selfPayWords(self2) {
|
|
44800
44879
|
const gas = self2.gas;
|
|
44801
44880
|
if (self2.status === "enough") {
|
|
44802
|
-
return `Costs no fee, only about ${gas.estimatedHuman} of network gas (the wallet asks to hold ${gas.requiredHuman}); it holds ${self2.balance?.human}, enough.`;
|
|
44881
|
+
return `Costs no fee, only about ${gas.estimatedHuman} of network gas (the wallet asks to hold ${gas.requiredHuman}, the estimate plus a fifth, and that is the figure to confirm); it holds ${self2.balance?.human}, enough.`;
|
|
44803
44882
|
}
|
|
44804
44883
|
return `Costs no fee, only network gas in ZTX${gas.estimatedHuman ? ` (about ${gas.estimatedHuman})` : ""}. ${self2.status === "short" ? "The wallet could NOT pay it right now" : "Whether the wallet can pay it is not known"}: ${clip2(self2.problems.join(" "), 400)}`;
|
|
44805
44884
|
}
|
|
@@ -44844,7 +44923,7 @@ async function offerGasChoice(deps, a, accept) {
|
|
|
44844
44923
|
} : {},
|
|
44845
44924
|
...self2 ? { self: { fee: "none", ...self2 } } : {}
|
|
44846
44925
|
},
|
|
44847
|
-
message: `CHOICE NEEDED \u2014 nothing was paid and nothing was written. ` + (both ? `There are two ways to pay to ${a.verb} this policy; ask the user which they want. ` : `Only ${quote ? "sponsored" : "self"} is available to ${a.verb} this policy right now; tell the user that and ask whether to go ahead that way. `) + `${sponsoredText} ${selfText} Say plainly what the wallet can pay right now. After the user has seen what the policy means (interpretation) and chosen, call ${a.tool} again with gasPayer ${both ? '"sponsored" or "self"' : only} and confirm: true. Never choose for the user and never pass confirm on your own judgement.`
|
|
44926
|
+
message: `CHOICE NEEDED \u2014 nothing was paid and nothing was written. ` + (both ? `There are two ways to pay to ${a.verb} this policy; ask the user which they want. ` : `Only ${quote ? "sponsored" : "self"} is available to ${a.verb} this policy right now; tell the user that and ask whether to go ahead that way. `) + `${sponsoredText} ${selfText} Say plainly what the wallet can pay right now. After the user has seen what the policy means (interpretation) and chosen, call ${a.tool} again with gasPayer ${both ? '"sponsored" or "self"' : only} and confirm: true${self2 && self2.gas.requiredRaw ? `; for "self" also pass maxGasRaw "${self2.gas.requiredRaw}", the gas figure shown above, the most the user agrees to` : ""}. Never choose for the user and never pass confirm on your own judgement.`
|
|
44848
44927
|
};
|
|
44849
44928
|
}
|
|
44850
44929
|
function chainMatchesCall(record2, call) {
|
|
@@ -44906,7 +44985,144 @@ async function confirmSelfWrite(deps, call, budgetMs, txHash) {
|
|
|
44906
44985
|
waited += wait;
|
|
44907
44986
|
}
|
|
44908
44987
|
}
|
|
44988
|
+
var selfPayLocks = /* @__PURE__ */ new Map();
|
|
44989
|
+
var SELF_PAY_LOCK_WAIT_MS = 15e3;
|
|
44990
|
+
async function withSelfPayLock(key, waitMs, fn) {
|
|
44991
|
+
const before = (selfPayLocks.get(key) ?? Promise.resolve()).catch(() => void 0);
|
|
44992
|
+
let release;
|
|
44993
|
+
const mine = new Promise((resolve2) => {
|
|
44994
|
+
release = resolve2;
|
|
44995
|
+
});
|
|
44996
|
+
const tail = Promise.all([before, mine]);
|
|
44997
|
+
selfPayLocks.set(key, tail);
|
|
44998
|
+
let timer;
|
|
44999
|
+
const acquired = await Promise.race([
|
|
45000
|
+
before.then(() => true),
|
|
45001
|
+
new Promise((resolve2) => {
|
|
45002
|
+
timer = setTimeout(() => resolve2(false), waitMs);
|
|
45003
|
+
})
|
|
45004
|
+
]);
|
|
45005
|
+
clearTimeout(timer);
|
|
45006
|
+
const done = () => {
|
|
45007
|
+
release();
|
|
45008
|
+
void tail.then(() => {
|
|
45009
|
+
if (selfPayLocks.get(key) === tail) selfPayLocks.delete(key);
|
|
45010
|
+
});
|
|
45011
|
+
};
|
|
45012
|
+
if (!acquired) {
|
|
45013
|
+
done();
|
|
45014
|
+
return { kind: "busy" };
|
|
45015
|
+
}
|
|
45016
|
+
try {
|
|
45017
|
+
return { kind: "ran", value: await fn() };
|
|
45018
|
+
} finally {
|
|
45019
|
+
done();
|
|
45020
|
+
}
|
|
45021
|
+
}
|
|
45022
|
+
var lockWaitMs = (deps) => deps.lockWaitMs ?? SELF_PAY_LOCK_WAIT_MS;
|
|
45023
|
+
var busyMessage = (policyKey, nothing) => `Another write, collect or removal for "${policyKey}" is in progress in this wallet, so ${nothing}. Ask again shortly.`;
|
|
45024
|
+
async function holdForSelfPaySend(deps, policyKey, nothing) {
|
|
45025
|
+
const inFlight = await resolveInFlight(deps, policyKey);
|
|
45026
|
+
if (inFlight.kind === "none") return void 0;
|
|
45027
|
+
if (inFlight.kind === "unreadable") {
|
|
45028
|
+
return {
|
|
45029
|
+
state: "unavailable",
|
|
45030
|
+
message: `Whether an earlier self-paid write for "${policyKey}" is still in flight could not be checked (${inFlight.detail ?? "the record of it could not be read"}), so ${nothing}. A transaction that is still in flight could overwrite this.`
|
|
45031
|
+
};
|
|
45032
|
+
}
|
|
45033
|
+
const m = inFlight.marker;
|
|
45034
|
+
return {
|
|
45035
|
+
state: "refused",
|
|
45036
|
+
txHash: m.txHash,
|
|
45037
|
+
message: `A self-paid ${m.operation === "UPDATE" ? "update" : "write"} for "${policyKey}" was sent at ${m.submittedAt} (transaction ${m.txHash}, nonce ${m.nonce}) and its outcome is not known yet. It can still be included until ledger ${m.ceilLedgerSeq}${inFlight.ledger ? ` (the ledger is now ${inFlight.ledger})` : ""}, and it would replace the whole policy, so ${nothing}. Read the policy with get_my_policy to see whether it landed, or ask again after ledger ${m.ceilLedgerSeq}, when it can no longer land.`
|
|
45038
|
+
};
|
|
45039
|
+
}
|
|
45040
|
+
async function resolveInFlight(deps, policyKey, mayClear = true) {
|
|
45041
|
+
const sp = deps.selfPay;
|
|
45042
|
+
if (!sp?.pendingSend) return { kind: "none" };
|
|
45043
|
+
let marker;
|
|
45044
|
+
try {
|
|
45045
|
+
marker = await bounded(deps, () => sp.pendingSend(policyKey));
|
|
45046
|
+
} catch (e) {
|
|
45047
|
+
return { kind: "unreadable", detail: clip2(e instanceof Error ? e.message : String(e), 1200) };
|
|
45048
|
+
}
|
|
45049
|
+
if (!marker) return { kind: "none" };
|
|
45050
|
+
const settled = marker;
|
|
45051
|
+
const clear = async () => {
|
|
45052
|
+
if (!mayClear) return;
|
|
45053
|
+
try {
|
|
45054
|
+
await sp.clearSend?.(policyKey, settled.txHash);
|
|
45055
|
+
} catch {
|
|
45056
|
+
}
|
|
45057
|
+
};
|
|
45058
|
+
if (sp.lookupTransaction) {
|
|
45059
|
+
try {
|
|
45060
|
+
const lookup = sp.lookupTransaction;
|
|
45061
|
+
const hash = marker.txHash;
|
|
45062
|
+
const tx = await bounded(deps, () => lookup(hash));
|
|
45063
|
+
if (tx.status === "success" || tx.status === "failed") {
|
|
45064
|
+
await clear();
|
|
45065
|
+
return { kind: "none" };
|
|
45066
|
+
}
|
|
45067
|
+
} catch {
|
|
45068
|
+
}
|
|
45069
|
+
}
|
|
45070
|
+
let ledger;
|
|
45071
|
+
if (sp.currentLedger) {
|
|
45072
|
+
try {
|
|
45073
|
+
const read2 = sp.currentLedger;
|
|
45074
|
+
ledger = await bounded(deps, () => read2());
|
|
45075
|
+
} catch {
|
|
45076
|
+
ledger = void 0;
|
|
45077
|
+
}
|
|
45078
|
+
}
|
|
45079
|
+
if (typeof ledger === "string" && /^\d{1,18}$/.test(ledger) && BigInt(ledger) > BigInt(marker.ceilLedgerSeq)) {
|
|
45080
|
+
await clear();
|
|
45081
|
+
return { kind: "none" };
|
|
45082
|
+
}
|
|
45083
|
+
return { kind: "blocked", marker, ...typeof ledger === "string" && /^\d{1,18}$/.test(ledger) ? { ledger } : {} };
|
|
45084
|
+
}
|
|
45085
|
+
var selfPayLockKey = (deps, policyKey) => `${deps.ownerAddress}
|
|
45086
|
+
${policyKey}`;
|
|
44909
45087
|
async function runSelfPay(deps, a) {
|
|
45088
|
+
if (!a.canWrite) return runSelfPayLocked(deps, a);
|
|
45089
|
+
const r = await withSelfPayLock(selfPayLockKey(deps, a.call.policyKey), lockWaitMs(deps), () => runSelfPayLocked(deps, a));
|
|
45090
|
+
if (r.kind === "ran") return r.value;
|
|
45091
|
+
return { state: "refused", paid: false, policyKey: a.call.policyKey, interpretation: a.interpretation, message: busyMessage(a.call.policyKey, "NOTHING WAS SENT") };
|
|
45092
|
+
}
|
|
45093
|
+
async function recoverInFlight(deps, client, a) {
|
|
45094
|
+
if (!a.canWrite) return recoverInFlightLocked(deps, client, a);
|
|
45095
|
+
const r = await withSelfPayLock(selfPayLockKey(deps, a.policyKey), lockWaitMs(deps), () => recoverInFlightLocked(deps, client, a));
|
|
45096
|
+
if (r.kind === "ran") return r.value;
|
|
45097
|
+
return {
|
|
45098
|
+
state: "refused",
|
|
45099
|
+
paid: true,
|
|
45100
|
+
paymentReceipt: a.pre.blobId,
|
|
45101
|
+
policyKey: a.policyKey,
|
|
45102
|
+
interpretation: a.interpretation,
|
|
45103
|
+
message: busyMessage(a.policyKey, COLLECT_HELD)
|
|
45104
|
+
};
|
|
45105
|
+
}
|
|
45106
|
+
async function runPaidFlow(deps, client, a) {
|
|
45107
|
+
const choosing = a.gasPayer === void 0 && deps.selfPay !== void 0;
|
|
45108
|
+
if (!a.canWrite || choosing) return runPaidFlowLocked(deps, client, a);
|
|
45109
|
+
const r = await withSelfPayLock(selfPayLockKey(deps, a.policyKey), lockWaitMs(deps), () => runPaidFlowLocked(deps, client, a));
|
|
45110
|
+
if (r.kind === "ran") return r.value;
|
|
45111
|
+
return { state: "refused", paid: false, policyKey: a.policyKey, interpretation: a.interpretation, message: busyMessage(a.policyKey, "NOTHING WAS PAID") };
|
|
45112
|
+
}
|
|
45113
|
+
var COLLECT_HELD = "NOTHING WAS COLLECTED (the earlier payment is not lost: do not pay again, ask again later)";
|
|
45114
|
+
async function collectUnlessSelfPayHeld(deps, receipt, budgetMs) {
|
|
45115
|
+
const r = await withSelfPayLock(selfPayLockKey(deps, receipt.policyKey), lockWaitMs(deps), async () => {
|
|
45116
|
+
const applied = await checkChainFirst(deps, receipt);
|
|
45117
|
+
if (applied) return applied;
|
|
45118
|
+
const held = await holdForSelfPaySend(deps, receipt.policyKey, COLLECT_HELD);
|
|
45119
|
+
if (held) return { ...held, paid: true, paymentReceipt: receipt.blobId, policyKey: receipt.policyKey };
|
|
45120
|
+
return pollCollect(deps, deps.client, receipt, budgetMs);
|
|
45121
|
+
});
|
|
45122
|
+
if (r.kind === "ran") return r.value;
|
|
45123
|
+
return { state: "refused", paid: true, paymentReceipt: receipt.blobId, policyKey: receipt.policyKey, message: busyMessage(receipt.policyKey, COLLECT_HELD) };
|
|
45124
|
+
}
|
|
45125
|
+
async function runSelfPayLocked(deps, a) {
|
|
44910
45126
|
const { call, interpretation } = a;
|
|
44911
45127
|
const policyKey = call.policyKey;
|
|
44912
45128
|
const done = a.verb === "write" ? "written" : "updated";
|
|
@@ -44934,6 +45150,22 @@ async function runSelfPay(deps, a) {
|
|
|
44934
45150
|
message: `A paid write for "${policyKey}" is still waiting to be finished (receipt ${waiting.blobId}), and a self-paid write now could be overwritten by its collect, or overwrite it. Finish it first with check_policy_write, then ask again. NOTHING WAS SENT.`
|
|
44935
45151
|
};
|
|
44936
45152
|
}
|
|
45153
|
+
const inFlight = await resolveInFlight(deps, policyKey, a.canWrite);
|
|
45154
|
+
if (inFlight.kind === "unreadable") {
|
|
45155
|
+
return unavailable(
|
|
45156
|
+
'Whether an earlier self-paid write for "' + policyKey + '" is still in flight could not be checked (' + (inFlight.detail ?? "the record of it could not be read") + "), so NOTHING WAS SENT."
|
|
45157
|
+
);
|
|
45158
|
+
}
|
|
45159
|
+
if (inFlight.kind === "blocked") {
|
|
45160
|
+
const m = inFlight.marker;
|
|
45161
|
+
return {
|
|
45162
|
+
state: "unknown",
|
|
45163
|
+
policyKey,
|
|
45164
|
+
txHash: m.txHash,
|
|
45165
|
+
interpretation,
|
|
45166
|
+
message: `A self-paid ${m.operation === "UPDATE" ? "update" : "write"} for "${policyKey}" was sent at ${m.submittedAt} (transaction ${m.txHash}, nonce ${m.nonce}) and its outcome is not known yet. It can still be included until ledger ${m.ceilLedgerSeq}${inFlight.ledger ? ` (the ledger is now ${inFlight.ledger})` : ""}, so NOTHING WAS SENT this time: a second transaction could land on top of it. Read the policy with get_my_policy to see whether it landed, or ask again after ledger ${m.ceilLedgerSeq}, when it can no longer land.`
|
|
45167
|
+
};
|
|
45168
|
+
}
|
|
44937
45169
|
let current;
|
|
44938
45170
|
try {
|
|
44939
45171
|
current = await readBounded(deps, policyKey);
|
|
@@ -44975,7 +45207,7 @@ async function runSelfPay(deps, a) {
|
|
|
44975
45207
|
policyKey,
|
|
44976
45208
|
interpretation,
|
|
44977
45209
|
gasOptions: { self: self2 },
|
|
44978
|
-
message: `QUOTE ONLY \u2014 nothing was sent and nothing was written. Paying the gas yourself: ${selfPayWords(assessment)} ` + (!a.dryRun && !a.confirmed ? `A policy is a spending limit, so a person must agree to it first: show the user what it means (interpretation) and the cost, and only after they say yes call ${a.tool} again with gasPayer "self" and confirm: true. Never pass confirm on your own judgement.` : assessment.status === "enough" ? `Ask again without dryRun to ${a.verb} it.` : "Fix that, then ask again without dryRun.")
|
|
45210
|
+
message: `QUOTE ONLY \u2014 nothing was sent and nothing was written. Paying the gas yourself: ${selfPayWords(assessment)} ` + (!a.dryRun && !a.confirmed ? `A policy is a spending limit, so a person must agree to it first: show the user what it means (interpretation) and the cost, and only after they say yes call ${a.tool} again with gasPayer "self"${assessment.gas.requiredRaw ? `, maxGasRaw "${assessment.gas.requiredRaw}" (the gas figure above)` : ""} and confirm: true. Never pass confirm on your own judgement.` : assessment.status === "enough" ? `Ask again without dryRun to ${a.verb} it, with gasPayer "self" and maxGasRaw "${assessment.gas.requiredRaw}".` : "Fix that, then ask again without dryRun.")
|
|
44979
45211
|
};
|
|
44980
45212
|
}
|
|
44981
45213
|
if (assessment.status !== "enough") {
|
|
@@ -44988,7 +45220,28 @@ async function runSelfPay(deps, a) {
|
|
|
44988
45220
|
message: `NOTHING WAS SENT: the wallet cannot confirm it can pay the gas. ${clip2(assessment.problems.join(" "), 500)} ` + (assessment.status === "short" ? 'Top up ZTX or use gasPayer "sponsored".' : 'Fix the cause above and ask again, or use gasPayer "sponsored".')
|
|
44989
45221
|
};
|
|
44990
45222
|
}
|
|
44991
|
-
const
|
|
45223
|
+
const requiredRaw = assessment.gas.requiredRaw;
|
|
45224
|
+
if (a.maxGasRaw === void 0) {
|
|
45225
|
+
return {
|
|
45226
|
+
state: "refused",
|
|
45227
|
+
paid: false,
|
|
45228
|
+
policyKey,
|
|
45229
|
+
interpretation,
|
|
45230
|
+
gasOptions: { self: self2 },
|
|
45231
|
+
message: `NOTHING WAS SENT: a self-paid write needs maxGasRaw, the most gas the user agreed to, so it can never cost more than they were shown. The gas the wallet asks to hold for this write is now ${assessment.gas.requiredHuman} (${requiredRaw} raw: the estimate plus a fifth). Show the user that figure and, if they agree, call ${a.tool} again with gasPayer "self", maxGasRaw "${requiredRaw}" and confirm: true.`
|
|
45232
|
+
};
|
|
45233
|
+
}
|
|
45234
|
+
if (BigInt(requiredRaw) > BigInt(a.maxGasRaw)) {
|
|
45235
|
+
return {
|
|
45236
|
+
state: "refused",
|
|
45237
|
+
paid: false,
|
|
45238
|
+
policyKey,
|
|
45239
|
+
interpretation,
|
|
45240
|
+
gasOptions: { self: self2 },
|
|
45241
|
+
message: `NOTHING WAS SENT: the gas the wallet asks to hold for this write is now ${assessment.gas.requiredHuman} (${requiredRaw} raw: the estimate plus a fifth), more than the ${a.maxGasRaw} raw the user agreed to. Show the user the new figure and, if they agree, call ${a.tool} again with gasPayer "self", maxGasRaw "${requiredRaw}" and confirm: true.`
|
|
45242
|
+
};
|
|
45243
|
+
}
|
|
45244
|
+
const sent = await deps.selfPay.execute(call, { maxFeeRaw: requiredRaw });
|
|
44992
45245
|
switch (sent.kind) {
|
|
44993
45246
|
case "rejected":
|
|
44994
45247
|
return { state: "refused", paid: false, policyKey, interpretation, message: `NOTHING WAS SENT and no gas was spent: ${sent.reason}. Fix the cause and ask again.` };
|
|
@@ -45018,11 +45271,18 @@ async function runSelfPay(deps, a) {
|
|
|
45018
45271
|
return {
|
|
45019
45272
|
state: "unknown",
|
|
45020
45273
|
policyKey,
|
|
45274
|
+
txHash: sent.txHash,
|
|
45021
45275
|
interpretation,
|
|
45022
|
-
message: `The transaction was sent but the node's answer never arrived (${sent.reason}), so it MAY be on chain and the gas MAY be spent. Do NOT send it again. Read the policy with get_my_policy to see whether it took effect (sent
|
|
45276
|
+
message: `The transaction was sent but the node's answer never arrived (${sent.reason}), so it MAY be on chain and the gas MAY be spent. Do NOT send it again. Read the policy with get_my_policy to see whether it took effect (transaction ${sent.txHash}, nonce ${sent.nonce}). It can be included only until ledger ${sent.ceilLedgerSeq}; this wallet remembers it and will not send another for this key until it has landed or can no longer land.`
|
|
45023
45277
|
};
|
|
45024
45278
|
case "submitted": {
|
|
45025
45279
|
const landed = await confirmSelfWrite(deps, call, a.pollBudgetMs, sent.txHash);
|
|
45280
|
+
if (landed.kind !== "not_yet") {
|
|
45281
|
+
try {
|
|
45282
|
+
await deps.selfPay.clearSend?.(policyKey, sent.txHash);
|
|
45283
|
+
} catch {
|
|
45284
|
+
}
|
|
45285
|
+
}
|
|
45026
45286
|
if (landed.kind === "failed") {
|
|
45027
45287
|
return {
|
|
45028
45288
|
state: "write_failed",
|
|
@@ -45054,7 +45314,7 @@ async function runSelfPay(deps, a) {
|
|
|
45054
45314
|
}
|
|
45055
45315
|
}
|
|
45056
45316
|
}
|
|
45057
|
-
async function
|
|
45317
|
+
async function runPaidFlowLocked(deps, client, a) {
|
|
45058
45318
|
const { policyKey, interpretation, dryRun, confirmed, canWrite } = a;
|
|
45059
45319
|
const accepts = a.challenge.accepts ?? [];
|
|
45060
45320
|
const offersChoice = a.gasPayer === void 0 && deps.selfPay !== void 0;
|
|
@@ -45088,6 +45348,8 @@ async function runPaidFlow(deps, client, a) {
|
|
|
45088
45348
|
message: `QUOTE ONLY \u2014 nothing was paid and nothing was written. The service asked for ${amountHuman ?? `${amount} of ${asset}`} to ${a.verb} this policy. That is what it asked for just now: it can change, and it is not a promise the payment will be allowed, because ` + (worded ? `balances can change and the payment cap is enforced again when paying. ${worded} ` : `the wallet's payment cap is applied only when paying. `) + (!dryRun && !confirmed ? `A policy is a spending limit, so a person must agree to it first: show the user what it means (interpretation) and the price, and only after they say yes call ${a.tool} again with confirm: true. Never pass confirm on your own judgement.` + (affordability?.verdict === "not_affordable" ? " Fix the affordability problem above first." : "") : affordability?.verdict === "not_affordable" ? "Fix that, then ask again without dryRun to deploy." : "Ask again without dryRun to deploy.")
|
|
45089
45349
|
};
|
|
45090
45350
|
}
|
|
45351
|
+
const held = await holdForSelfPaySend(deps, policyKey, "NOTHING WAS PAID");
|
|
45352
|
+
if (held) return { ...held, paid: false, policyKey, interpretation };
|
|
45091
45353
|
let paymentHeader;
|
|
45092
45354
|
try {
|
|
45093
45355
|
paymentHeader = await deps.pay(accept);
|
|
@@ -45161,14 +45423,14 @@ async function checkPolicyWrite(deps, input = {}) {
|
|
|
45161
45423
|
}
|
|
45162
45424
|
if (pending.length > 1) {
|
|
45163
45425
|
const others = pending.slice(1).map((r) => `${r.policyKey} (${r.blobId})`).join(", ");
|
|
45164
|
-
const result = await
|
|
45426
|
+
const result = await collectUnlessSelfPayHeld(deps, receipt, input.pollBudgetMs ?? DEFAULT_BUDGET_MS);
|
|
45165
45427
|
return {
|
|
45166
45428
|
...result,
|
|
45167
45429
|
message: `${result.message} NOTE: ${pending.length} paid-for writes are on file and this checked the most recent one only. The others are: ${others}. Check each by passing its receipt.`
|
|
45168
45430
|
};
|
|
45169
45431
|
}
|
|
45170
45432
|
}
|
|
45171
|
-
return
|
|
45433
|
+
return collectUnlessSelfPayHeld(deps, receipt, input.pollBudgetMs ?? DEFAULT_BUDGET_MS);
|
|
45172
45434
|
}
|
|
45173
45435
|
async function checkChainFirst(deps, receipt) {
|
|
45174
45436
|
const shows = await chainShows(deps, receipt);
|
|
@@ -45189,6 +45451,8 @@ function blockString(value) {
|
|
|
45189
45451
|
async function updatePolicy(deps, input) {
|
|
45190
45452
|
const gas = parseGasPayer(input?.gasPayer);
|
|
45191
45453
|
if (!gas.ok) return refusedGasPayer(input?.policyKey);
|
|
45454
|
+
const maxGas = parseMaxGasRaw(input?.maxGasRaw);
|
|
45455
|
+
if (!maxGas.ok) return refusedMaxGas(input?.policyKey);
|
|
45192
45456
|
const client = deps.client;
|
|
45193
45457
|
if (!client && gas.gasPayer !== "self" && !(gas.gasPayer === void 0 && deps.selfPay)) {
|
|
45194
45458
|
return unavailable(
|
|
@@ -45338,7 +45602,8 @@ async function updatePolicy(deps, input) {
|
|
|
45338
45602
|
pollBudgetMs: input.pollBudgetMs ?? DEFAULT_BUDGET_MS,
|
|
45339
45603
|
tool: "update_policy",
|
|
45340
45604
|
verb: "update",
|
|
45341
|
-
...blocker ? { unavailable: blocker } : {}
|
|
45605
|
+
...blocker ? { unavailable: blocker } : {},
|
|
45606
|
+
...maxGas.maxGasRaw ? { maxGasRaw: maxGas.maxGasRaw } : {}
|
|
45342
45607
|
});
|
|
45343
45608
|
}
|
|
45344
45609
|
const selfUnavailable = blocker ?? (!selfCall ? NO_WINDOW_REASON : void 0);
|
|
@@ -45422,6 +45687,12 @@ var REMOVE_CONSEQUENCE = "Removing a policy removes every limit it set, and Wall
|
|
|
45422
45687
|
var NEVER_TWICE = "Calling remove_policy again is expected to be safe: the service is expected not to submit a second removal for a policy that is already being removed.";
|
|
45423
45688
|
var IN_PROGRESS_MESSAGE = (detail) => `A write for this policy has ALREADY BEEN PAID FOR and is still being completed by the service (${detail}). Do not pay again, and nothing was paid by this call. If this wallet holds its receipt, check_policy_write finishes it; otherwise wait and read the policy back later with get_my_policy to see whether it took effect.`;
|
|
45424
45689
|
async function removePolicy(deps, input) {
|
|
45690
|
+
if (!(input?.confirm === true && typeof input.policyKey === "string" && input.policyKey !== "")) return removePolicyLocked(deps, input);
|
|
45691
|
+
const r = await withSelfPayLock(selfPayLockKey(deps, input.policyKey), lockWaitMs(deps), () => removePolicyLocked(deps, input));
|
|
45692
|
+
if (r.kind === "ran") return r.value;
|
|
45693
|
+
return { state: "refused", policyKey: input.policyKey, message: busyMessage(input.policyKey, "NOTHING WAS REMOVED") };
|
|
45694
|
+
}
|
|
45695
|
+
async function removePolicyLocked(deps, input) {
|
|
45425
45696
|
const client = deps.client;
|
|
45426
45697
|
if (!client) {
|
|
45427
45698
|
return {
|
|
@@ -45457,6 +45728,8 @@ async function removePolicy(deps, input) {
|
|
|
45457
45728
|
message: `NOTHING WAS REMOVED. ${REMOVE_CONSEQUENCE} Show the user what the policy "${input.policyKey}" currently limits${attributes ? " (currentAttributes)" : ""} and what removing it means, and only if they clearly agree call remove_policy again with confirm: true. Never pass confirm on your own judgement.`
|
|
45458
45729
|
};
|
|
45459
45730
|
}
|
|
45731
|
+
const heldRemove = await holdForSelfPaySend(deps, input.policyKey, "NOTHING WAS REMOVED");
|
|
45732
|
+
if (heldRemove) return { state: heldRemove.state, policyKey: input.policyKey, message: heldRemove.message };
|
|
45460
45733
|
const result = await client.remove(deps.ownerAddress, input.policyKey, deps.hsmPassword);
|
|
45461
45734
|
switch (result.kind) {
|
|
45462
45735
|
case "not_found":
|
|
@@ -45513,11 +45786,11 @@ async function removePolicy(deps, input) {
|
|
|
45513
45786
|
const started = (deps.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
45514
45787
|
let polls = 0;
|
|
45515
45788
|
let waited = 0;
|
|
45516
|
-
let
|
|
45789
|
+
let unreadable2 = false;
|
|
45517
45790
|
for (; ; ) {
|
|
45518
45791
|
const read2 = await deps.readPolicy(input.policyKey);
|
|
45519
45792
|
if ("error" in read2) {
|
|
45520
|
-
|
|
45793
|
+
unreadable2 = true;
|
|
45521
45794
|
} else if (read2.found === false) {
|
|
45522
45795
|
return {
|
|
45523
45796
|
state: "removed",
|
|
@@ -45526,7 +45799,7 @@ async function removePolicy(deps, input) {
|
|
|
45526
45799
|
message: `The policy "${input.policyKey}" is removed: the chain no longer holds it. Its limits no longer apply.`
|
|
45527
45800
|
};
|
|
45528
45801
|
} else {
|
|
45529
|
-
|
|
45802
|
+
unreadable2 = false;
|
|
45530
45803
|
}
|
|
45531
45804
|
const elapsed = Math.max((deps.now?.() ?? /* @__PURE__ */ new Date()).getTime() - started, waited);
|
|
45532
45805
|
if (++polls >= MAX_POLLS || elapsed + wait >= budget) break;
|
|
@@ -45534,7 +45807,7 @@ async function removePolicy(deps, input) {
|
|
|
45534
45807
|
waited += wait;
|
|
45535
45808
|
}
|
|
45536
45809
|
return submittedMessage(
|
|
45537
|
-
|
|
45810
|
+
unreadable2 ? "the chain could not be read to confirm it." : "the chain still shows the policy, so the block has not confirmed the removal yet."
|
|
45538
45811
|
);
|
|
45539
45812
|
}
|
|
45540
45813
|
|
|
@@ -45759,6 +46032,17 @@ async function transferToken(deps, opts) {
|
|
|
45759
46032
|
var DEFAULT_EXPIRY_MINUTES = 5;
|
|
45760
46033
|
var MAX_EXPIRY_MINUTES = 60;
|
|
45761
46034
|
var MAX_REVEAL_PATHS = 50;
|
|
46035
|
+
function qrGuidance(qrCodePath, hasQrImage) {
|
|
46036
|
+
const fallback = "If you cannot attach an image in this chat, give the user the link and say plainly that you could not send the QR code.";
|
|
46037
|
+
const never2 = "Never send any other image as the QR code, and never call another image a QR code: the credential's pass design is not one.";
|
|
46038
|
+
if (qrCodePath !== void 0) {
|
|
46039
|
+
return `Send the user the QR code by attaching the file at ${qrCodePath} (the same image is also returned with this result, as its first image; any other image is not the QR code). ${never2} ${fallback} Also give them the link.`;
|
|
46040
|
+
}
|
|
46041
|
+
if (hasQrImage) {
|
|
46042
|
+
return `There is no QR image file this time, so give the user the link. The QR code is the first image returned with this result; any other image is not the QR code. ${never2} If your channel cannot show images, say plainly that you could not send the QR code.`;
|
|
46043
|
+
}
|
|
46044
|
+
return `No QR code could be made this time, so give the user the link and say plainly that you could not send a QR code. Any image returned with this result is not the QR code. ${never2}`;
|
|
46045
|
+
}
|
|
45762
46046
|
function buildVerificationLink(template, referenceId) {
|
|
45763
46047
|
const problem = checkVerifyLinkTemplate(template);
|
|
45764
46048
|
if (problem) throw new Error(problem);
|
|
@@ -45852,8 +46136,20 @@ async function createVerificationLink(input, deps) {
|
|
|
45852
46136
|
const expiresAt = new Date(startedAt + expiryMinutes * 6e4).toISOString();
|
|
45853
46137
|
let qrCodePngBase64;
|
|
45854
46138
|
let qrError;
|
|
46139
|
+
let qrCodePath;
|
|
46140
|
+
let qrFileError;
|
|
45855
46141
|
try {
|
|
45856
|
-
|
|
46142
|
+
const png = await deps.renderQr(link);
|
|
46143
|
+
if (png.length === 0) throw new Error("the QR renderer returned nothing");
|
|
46144
|
+
qrCodePngBase64 = png.toString("base64");
|
|
46145
|
+
if (deps.saveQr) {
|
|
46146
|
+
try {
|
|
46147
|
+
qrCodePath = await deps.saveQr(png, referenceId, startedAt + expiryMinutes * 6e4);
|
|
46148
|
+
} catch (e) {
|
|
46149
|
+
const code = e?.code;
|
|
46150
|
+
qrFileError = typeof code === "string" && /^[A-Z0-9_]{1,32}$/.test(code) ? `could not write the QR file (${code})` : "could not write the QR file";
|
|
46151
|
+
}
|
|
46152
|
+
}
|
|
45857
46153
|
} catch (e) {
|
|
45858
46154
|
qrError = messageOf(e);
|
|
45859
46155
|
}
|
|
@@ -45866,7 +46162,9 @@ async function createVerificationLink(input, deps) {
|
|
|
45866
46162
|
revealed: revealed.revealed,
|
|
45867
46163
|
...qrCodePngBase64 !== void 0 ? { qrCodePngBase64 } : {},
|
|
45868
46164
|
...qrError !== void 0 ? { qrError } : {},
|
|
45869
|
-
|
|
46165
|
+
...qrCodePath !== void 0 ? { qrCodePath } : {},
|
|
46166
|
+
...qrFileError !== void 0 ? { qrFileError } : {},
|
|
46167
|
+
message: `${qrGuidance(qrCodePath, qrCodePngBase64 !== void 0)} Tell them to open the link in the MyID app (they need MyID installed first). It stops working at about ${expiresAt}, ${expiryMinutes} minute${expiryMinutes === 1 ? "" : "s"} after it was created \u2014 if it lapses, create a new one. This ${revealed.revealed === "all" ? "reveals the whole credential" : `reveals: ${revealed.revealed.map((p) => JSON.stringify(p)).join(", ")}`}. Show it only to the user: whoever holds the link can open what it reveals until then.`
|
|
45870
46168
|
};
|
|
45871
46169
|
}
|
|
45872
46170
|
|
|
@@ -45941,6 +46239,7 @@ function createTools(deps) {
|
|
|
45941
46239
|
let vc = input.vc;
|
|
45942
46240
|
let credentialUsed = "the credential you supplied";
|
|
45943
46241
|
let kind;
|
|
46242
|
+
let savedPassImages;
|
|
45944
46243
|
const wantsDefault = (input.revealAttribute === void 0 || input.revealAttribute.length === 0) && input.revealAll !== true;
|
|
45945
46244
|
const verifiedId = deps.config.aiBirthcertVerifiedTemplateId;
|
|
45946
46245
|
const basicId = resolveTemplateAlias("ai birthcert", deps.config.network);
|
|
@@ -45956,11 +46255,13 @@ function createTools(deps) {
|
|
|
45956
46255
|
};
|
|
45957
46256
|
}
|
|
45958
46257
|
vc = chosen.vc;
|
|
46258
|
+
savedPassImages = chosen.vcPassImagePaths;
|
|
45959
46259
|
credentialUsed = verified ? "Verified AI Birthcert" : "Basic AI Birthcert";
|
|
45960
46260
|
kind = verified ? "verified" : "basic";
|
|
45961
46261
|
} else if (wantsDefault && typeof vc === "object" && vc !== null && !Array.isArray(vc)) {
|
|
45962
46262
|
const id = vc.id;
|
|
45963
46263
|
const match = typeof id === "string" ? (await loadValidCachedCredentials(deps.cache)).find((e) => e.vc?.id === id) : void 0;
|
|
46264
|
+
savedPassImages = match?.vcPassImagePaths;
|
|
45964
46265
|
if (match && verifiedId && match.templateId === verifiedId) kind = "verified";
|
|
45965
46266
|
else if (match && basicId && match.templateId === basicId) kind = "basic";
|
|
45966
46267
|
}
|
|
@@ -45996,12 +46297,16 @@ function createTools(deps) {
|
|
|
45996
46297
|
expiryMinutes: input.expiryMinutes
|
|
45997
46298
|
});
|
|
45998
46299
|
if (!result.created) return result;
|
|
46300
|
+
const passImages = savedPassImages ? await existingFiles(savedPassImages) : [];
|
|
45999
46301
|
const basicNote = kind === "basic" ? " This is a Basic AI Birthcert: it shows the agent's registered username only and does not mean the owner was verified, so do not describe the agent as owner-verified." : "";
|
|
46302
|
+
const passNote = passImages.length > 0 ? " The credential's pass design image is also returned (vcPassImagePaths): if the user wants to see it, send it as its own picture, captioned as the credential's pass design. It is not the QR code." : "";
|
|
46303
|
+
const extra = basicNote + passNote;
|
|
46000
46304
|
return {
|
|
46001
46305
|
...result,
|
|
46002
46306
|
credentialUsed,
|
|
46003
46307
|
...revealedByDefault ? { revealedByDefault: true } : {},
|
|
46004
|
-
...
|
|
46308
|
+
...passImages.length > 0 ? { vcPassImagePaths: passImages } : {},
|
|
46309
|
+
...extra ? { message: result.message + extra } : {}
|
|
46005
46310
|
};
|
|
46006
46311
|
},
|
|
46007
46312
|
pay_and_fetch(input) {
|
|
@@ -46090,7 +46395,14 @@ function createTools(deps) {
|
|
|
46090
46395
|
*/
|
|
46091
46396
|
async credential_preflight(input) {
|
|
46092
46397
|
if (input.credential === VERIFIED_AI_BIRTHCERT && !deps.verifyAiBirthcert) {
|
|
46093
|
-
return {
|
|
46398
|
+
return {
|
|
46399
|
+
credential: input.credential,
|
|
46400
|
+
ready: false,
|
|
46401
|
+
balances: [],
|
|
46402
|
+
blockers: [AI_BIRTHCERT_NOT_CONFIGURED_ERROR],
|
|
46403
|
+
notChecked: [],
|
|
46404
|
+
choice: aiBirthcertChoice("verified", { unavailable: true })
|
|
46405
|
+
};
|
|
46094
46406
|
}
|
|
46095
46407
|
if (!deps.queryTokenBalance) {
|
|
46096
46408
|
return {
|
|
@@ -46101,8 +46413,11 @@ function createTools(deps) {
|
|
|
46101
46413
|
notChecked: []
|
|
46102
46414
|
};
|
|
46103
46415
|
}
|
|
46416
|
+
const basicId = resolveTemplateAlias("ai birthcert", deps.config.network);
|
|
46417
|
+
const isBasicAiBirthcert = (credential) => basicId !== void 0 && (credential === basicId || resolveTemplateAlias(credential, deps.config.network) === basicId);
|
|
46104
46418
|
return credentialPreflight(
|
|
46105
46419
|
{
|
|
46420
|
+
isBasicAiBirthcert,
|
|
46106
46421
|
quoteVerified: (q) => deps.verifyAiBirthcert.request(q),
|
|
46107
46422
|
quoteTemplate: async (templateId) => {
|
|
46108
46423
|
const resolved = resolveTemplateAlias(templateId, deps.config.network) ?? templateId;
|
|
@@ -46365,6 +46680,17 @@ async function priceTemplate(deps, tools, templateId) {
|
|
|
46365
46680
|
...schema ? { schema } : {}
|
|
46366
46681
|
};
|
|
46367
46682
|
}
|
|
46683
|
+
async function existingFiles(paths) {
|
|
46684
|
+
const found = [];
|
|
46685
|
+
for (const path of paths) {
|
|
46686
|
+
try {
|
|
46687
|
+
await (0, import_promises4.access)(path);
|
|
46688
|
+
found.push(path);
|
|
46689
|
+
} catch {
|
|
46690
|
+
}
|
|
46691
|
+
}
|
|
46692
|
+
return found;
|
|
46693
|
+
}
|
|
46368
46694
|
|
|
46369
46695
|
// src/held-credential.ts
|
|
46370
46696
|
function createHeldCredentialFinder(deps) {
|
|
@@ -47097,6 +47423,57 @@ async function renderQrPng(text) {
|
|
|
47097
47423
|
]);
|
|
47098
47424
|
}
|
|
47099
47425
|
|
|
47426
|
+
// src/qr-file-store.ts
|
|
47427
|
+
var import_node_crypto8 = require("node:crypto");
|
|
47428
|
+
var import_promises5 = require("node:fs/promises");
|
|
47429
|
+
var import_node_path5 = require("node:path");
|
|
47430
|
+
var PREFIX = "verification-qr-";
|
|
47431
|
+
var OWN_FILE = /^verification-qr-[a-z0-9]{1,8}-(\d+)-[a-f0-9]{6}\.png$/;
|
|
47432
|
+
function tagFor(referenceId) {
|
|
47433
|
+
const tag = referenceId.toLowerCase().replace(/^v2-/, "").replace(/[^a-z0-9]/g, "").slice(0, 8);
|
|
47434
|
+
return tag === "" ? "qr" : tag;
|
|
47435
|
+
}
|
|
47436
|
+
function createFsQrFileStore(dir, opts = {}) {
|
|
47437
|
+
const now = opts.now ?? Date.now;
|
|
47438
|
+
const randomSuffix = opts.randomSuffix ?? (() => (0, import_node_crypto8.randomBytes)(3).toString("hex"));
|
|
47439
|
+
const root = (0, import_node_path5.resolve)(dir);
|
|
47440
|
+
async function removeExpired() {
|
|
47441
|
+
let names;
|
|
47442
|
+
try {
|
|
47443
|
+
names = await (0, import_promises5.readdir)(root);
|
|
47444
|
+
} catch {
|
|
47445
|
+
return;
|
|
47446
|
+
}
|
|
47447
|
+
const at = now();
|
|
47448
|
+
for (const name of names) {
|
|
47449
|
+
const m = OWN_FILE.exec(name);
|
|
47450
|
+
if (!m) continue;
|
|
47451
|
+
if (Number(m[1]) > at) continue;
|
|
47452
|
+
try {
|
|
47453
|
+
await (0, import_promises5.unlink)((0, import_node_path5.join)(root, name));
|
|
47454
|
+
} catch {
|
|
47455
|
+
}
|
|
47456
|
+
}
|
|
47457
|
+
}
|
|
47458
|
+
return {
|
|
47459
|
+
async save(png, referenceId, expiresAtMs) {
|
|
47460
|
+
await (0, import_promises5.mkdir)(root, { recursive: true, mode: 448 });
|
|
47461
|
+
await removeExpired();
|
|
47462
|
+
for (let attempt = 0; ; attempt++) {
|
|
47463
|
+
const name = `${PREFIX}${tagFor(referenceId)}-${Math.floor(expiresAtMs)}-${randomSuffix()}.png`;
|
|
47464
|
+
const path = (0, import_node_path5.join)(root, name);
|
|
47465
|
+
try {
|
|
47466
|
+
await (0, import_promises5.writeFile)(path, png, { mode: 384, flag: "wx" });
|
|
47467
|
+
return path;
|
|
47468
|
+
} catch (e) {
|
|
47469
|
+
if (e.code !== "EEXIST" || attempt >= 4) throw e;
|
|
47470
|
+
}
|
|
47471
|
+
}
|
|
47472
|
+
},
|
|
47473
|
+
sweep: removeExpired
|
|
47474
|
+
};
|
|
47475
|
+
}
|
|
47476
|
+
|
|
47100
47477
|
// src/policy-cap-bypass.ts
|
|
47101
47478
|
var NATIVE2 = "ZTX";
|
|
47102
47479
|
var RAW_AMOUNT2 = /^\d{1,77}$/;
|
|
@@ -47163,8 +47540,8 @@ function createPolicyGovernsCheck(options) {
|
|
|
47163
47540
|
const pending = inFlight;
|
|
47164
47541
|
pending.catch(() => void 0);
|
|
47165
47542
|
let timer;
|
|
47166
|
-
const timedOut = new Promise((
|
|
47167
|
-
timer = setTimeout(() =>
|
|
47543
|
+
const timedOut = new Promise((resolve2) => {
|
|
47544
|
+
timer = setTimeout(() => resolve2(TIMED_OUT), timeoutMs);
|
|
47168
47545
|
});
|
|
47169
47546
|
try {
|
|
47170
47547
|
const result = await Promise.race([pending, timedOut]);
|
|
@@ -47262,12 +47639,12 @@ function configPathFrom(argv) {
|
|
|
47262
47639
|
if (!path || path.startsWith("--")) throw new Error("agentic-wallet-mcp: --config requires a file path");
|
|
47263
47640
|
return path;
|
|
47264
47641
|
}
|
|
47265
|
-
function loadConfigFileEnv(argv,
|
|
47642
|
+
function loadConfigFileEnv(argv, readFile8) {
|
|
47266
47643
|
const path = configPathFrom(argv);
|
|
47267
47644
|
if (path === null) return {};
|
|
47268
47645
|
let raw;
|
|
47269
47646
|
try {
|
|
47270
|
-
raw =
|
|
47647
|
+
raw = readFile8(path);
|
|
47271
47648
|
} catch (e) {
|
|
47272
47649
|
throw new Error(`agentic-wallet-mcp: cannot read config file ${path}: ${e.message}`);
|
|
47273
47650
|
}
|
|
@@ -47299,9 +47676,9 @@ function loadConfigFileEnv(argv, readFile7) {
|
|
|
47299
47676
|
}
|
|
47300
47677
|
|
|
47301
47678
|
// src/hsm-password.ts
|
|
47302
|
-
var
|
|
47679
|
+
var import_node_crypto9 = require("node:crypto");
|
|
47303
47680
|
function generateHsmPassword() {
|
|
47304
|
-
return (0,
|
|
47681
|
+
return (0, import_node_crypto9.randomBytes)(24).toString("base64url");
|
|
47305
47682
|
}
|
|
47306
47683
|
|
|
47307
47684
|
// src/export-credentials.ts
|
|
@@ -47338,8 +47715,8 @@ permanently inaccessible.
|
|
|
47338
47715
|
}
|
|
47339
47716
|
|
|
47340
47717
|
// src/clients/account-store.ts
|
|
47341
|
-
var
|
|
47342
|
-
var
|
|
47718
|
+
var import_promises6 = require("node:fs/promises");
|
|
47719
|
+
var import_node_path6 = require("node:path");
|
|
47343
47720
|
function isStoredAccountShape(value) {
|
|
47344
47721
|
return typeof value === "object" && value !== null && typeof value.zetrixAddress === "string" && typeof value.holderDid === "string" && typeof value.hsmPassword === "string";
|
|
47345
47722
|
}
|
|
@@ -47347,7 +47724,7 @@ function createFsAccountStore(filePath) {
|
|
|
47347
47724
|
return {
|
|
47348
47725
|
async get() {
|
|
47349
47726
|
try {
|
|
47350
|
-
const raw = await (0,
|
|
47727
|
+
const raw = await (0, import_promises6.readFile)(filePath, "utf8");
|
|
47351
47728
|
const parsed = JSON.parse(raw);
|
|
47352
47729
|
return isStoredAccountShape(parsed) ? parsed : null;
|
|
47353
47730
|
} catch {
|
|
@@ -47355,8 +47732,8 @@ function createFsAccountStore(filePath) {
|
|
|
47355
47732
|
}
|
|
47356
47733
|
},
|
|
47357
47734
|
async set(account) {
|
|
47358
|
-
await (0,
|
|
47359
|
-
await (0,
|
|
47735
|
+
await (0, import_promises6.mkdir)((0, import_node_path6.dirname)(filePath), { recursive: true, mode: 448 });
|
|
47736
|
+
await (0, import_promises6.writeFile)(filePath, JSON.stringify(account, null, 2), { encoding: "utf8", mode: 384 });
|
|
47360
47737
|
}
|
|
47361
47738
|
};
|
|
47362
47739
|
}
|
|
@@ -47374,13 +47751,13 @@ function splitQrCode(result) {
|
|
|
47374
47751
|
if (typeof qrCodePngBase64 !== "string" || qrCodePngBase64 === "") return { text: result };
|
|
47375
47752
|
return { text: rest, qrCodePngBase64 };
|
|
47376
47753
|
}
|
|
47377
|
-
async function buildToolContent(result,
|
|
47754
|
+
async function buildToolContent(result, readFile8) {
|
|
47378
47755
|
const { text, qrCodePngBase64 } = splitQrCode(result);
|
|
47379
47756
|
const blocks = [{ type: "text", text: JSON.stringify(text) }];
|
|
47380
47757
|
if (qrCodePngBase64 !== void 0) blocks.push({ type: "image", data: qrCodePngBase64, mimeType: "image/png" });
|
|
47381
47758
|
for (const path of extractVcPassImagePaths(result)) {
|
|
47382
47759
|
try {
|
|
47383
|
-
const data = await
|
|
47760
|
+
const data = await readFile8(path);
|
|
47384
47761
|
blocks.push({ type: "image", data: data.toString("base64"), mimeType: "image/png" });
|
|
47385
47762
|
} catch {
|
|
47386
47763
|
}
|
|
@@ -47529,8 +47906,8 @@ var SsivcClient = class {
|
|
|
47529
47906
|
};
|
|
47530
47907
|
|
|
47531
47908
|
// src/clients/ssivc-session-store.ts
|
|
47532
|
-
var
|
|
47533
|
-
var
|
|
47909
|
+
var import_promises7 = require("node:fs/promises");
|
|
47910
|
+
var import_node_path7 = require("node:path");
|
|
47534
47911
|
var isAbsentOrString = (v) => v === void 0 || typeof v === "string";
|
|
47535
47912
|
function isStoredSessionShape(value) {
|
|
47536
47913
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -47541,7 +47918,7 @@ function createFsSsivcSessionStore(filePath) {
|
|
|
47541
47918
|
return {
|
|
47542
47919
|
async get() {
|
|
47543
47920
|
try {
|
|
47544
|
-
const raw = await (0,
|
|
47921
|
+
const raw = await (0, import_promises7.readFile)(filePath, "utf8");
|
|
47545
47922
|
const parsed = JSON.parse(raw);
|
|
47546
47923
|
return isStoredSessionShape(parsed) ? parsed : null;
|
|
47547
47924
|
} catch (err) {
|
|
@@ -47551,23 +47928,23 @@ function createFsSsivcSessionStore(filePath) {
|
|
|
47551
47928
|
}
|
|
47552
47929
|
},
|
|
47553
47930
|
async set(session) {
|
|
47554
|
-
await (0,
|
|
47931
|
+
await (0, import_promises7.mkdir)((0, import_node_path7.dirname)(filePath), { recursive: true, mode: 448 });
|
|
47555
47932
|
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
47556
|
-
await (0,
|
|
47557
|
-
await (0,
|
|
47933
|
+
await (0, import_promises7.writeFile)(tmpPath, JSON.stringify(session, null, 2), { encoding: "utf8", mode: 384 });
|
|
47934
|
+
await (0, import_promises7.rename)(tmpPath, filePath);
|
|
47558
47935
|
},
|
|
47559
47936
|
async clear() {
|
|
47560
|
-
await (0,
|
|
47937
|
+
await (0, import_promises7.rm)(filePath, { force: true });
|
|
47561
47938
|
}
|
|
47562
47939
|
};
|
|
47563
47940
|
}
|
|
47564
47941
|
|
|
47565
47942
|
// src/clients/ssivc-download-quarantine-store.ts
|
|
47566
|
-
var
|
|
47567
|
-
var
|
|
47568
|
-
var
|
|
47943
|
+
var import_node_crypto10 = require("node:crypto");
|
|
47944
|
+
var import_promises8 = require("node:fs/promises");
|
|
47945
|
+
var import_node_path8 = require("node:path");
|
|
47569
47946
|
function quarantineFileName(vcId) {
|
|
47570
|
-
return `${(0,
|
|
47947
|
+
return `${(0, import_node_crypto10.createHash)("sha256").update(vcId).digest("hex")}.json`;
|
|
47571
47948
|
}
|
|
47572
47949
|
function isQuarantinedDownloadShape(value) {
|
|
47573
47950
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -47575,7 +47952,7 @@ function isQuarantinedDownloadShape(value) {
|
|
|
47575
47952
|
return typeof v.vcId === "string" && "entries" in v && typeof v.downloadedAt === "string";
|
|
47576
47953
|
}
|
|
47577
47954
|
function createFsDownloadQuarantineStore(baseDir) {
|
|
47578
|
-
const pathFor = (vcId) => (0,
|
|
47955
|
+
const pathFor = (vcId) => (0, import_node_path8.join)(baseDir, quarantineFileName(vcId));
|
|
47579
47956
|
const locks = /* @__PURE__ */ new Map();
|
|
47580
47957
|
return {
|
|
47581
47958
|
filePathFor: pathFor,
|
|
@@ -47590,7 +47967,7 @@ function createFsDownloadQuarantineStore(baseDir) {
|
|
|
47590
47967
|
},
|
|
47591
47968
|
async get(vcId) {
|
|
47592
47969
|
try {
|
|
47593
|
-
const raw = await (0,
|
|
47970
|
+
const raw = await (0, import_promises8.readFile)(pathFor(vcId), "utf8");
|
|
47594
47971
|
const parsed = JSON.parse(raw);
|
|
47595
47972
|
return isQuarantinedDownloadShape(parsed) ? parsed : null;
|
|
47596
47973
|
} catch {
|
|
@@ -47598,17 +47975,17 @@ function createFsDownloadQuarantineStore(baseDir) {
|
|
|
47598
47975
|
}
|
|
47599
47976
|
},
|
|
47600
47977
|
async set(entry) {
|
|
47601
|
-
await (0,
|
|
47978
|
+
await (0, import_promises8.mkdir)(baseDir, { recursive: true, mode: 448 });
|
|
47602
47979
|
const filePath = pathFor(entry.vcId);
|
|
47603
47980
|
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
47604
|
-
await (0,
|
|
47605
|
-
await (0,
|
|
47981
|
+
await (0, import_promises8.writeFile)(tmpPath, JSON.stringify(entry, null, 2), { encoding: "utf8", mode: 384 });
|
|
47982
|
+
await (0, import_promises8.rename)(tmpPath, filePath);
|
|
47606
47983
|
}
|
|
47607
47984
|
};
|
|
47608
47985
|
}
|
|
47609
47986
|
|
|
47610
47987
|
// src/orchestrator/verify-ai-birthcert.ts
|
|
47611
|
-
var
|
|
47988
|
+
var import_node_crypto11 = require("node:crypto");
|
|
47612
47989
|
|
|
47613
47990
|
// src/canonical-json.ts
|
|
47614
47991
|
function canonicalizeJson(value) {
|
|
@@ -47734,7 +48111,7 @@ function retryDelayMs(retryAfterSeconds) {
|
|
|
47734
48111
|
if (!Number.isFinite(retryAfterSeconds) || retryAfterSeconds <= 0) return FALLBACK_RETRY_DELAY_MS;
|
|
47735
48112
|
return Math.min(retryAfterSeconds * 1e3, MAX_RETRY_DELAY_MS);
|
|
47736
48113
|
}
|
|
47737
|
-
var defaultSleep = (ms) => new Promise((
|
|
48114
|
+
var defaultSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
47738
48115
|
var SettlementStillQueuedError = class extends Error {
|
|
47739
48116
|
constructor(message, paymentReceipt) {
|
|
47740
48117
|
super(message);
|
|
@@ -48001,7 +48378,7 @@ async function buildSessionBody(deps, agentName, input) {
|
|
|
48001
48378
|
ownerReference: deps.holderDid
|
|
48002
48379
|
};
|
|
48003
48380
|
Object.assign(fields, optionalRequestFields(input));
|
|
48004
|
-
const digestHex = (0,
|
|
48381
|
+
const digestHex = (0, import_node_crypto11.createHash)("sha256").update(canonicalizeJson(fields), "utf8").digest("hex");
|
|
48005
48382
|
const { signBlob: signedData } = await deps.signHexBlob(digestHex);
|
|
48006
48383
|
return { ...fields, signedData };
|
|
48007
48384
|
}
|
|
@@ -48945,6 +49322,100 @@ function clip3(text, max = 200) {
|
|
|
48945
49322
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
48946
49323
|
}
|
|
48947
49324
|
|
|
49325
|
+
// src/clients/self-pay-marker-store.ts
|
|
49326
|
+
var import_node_crypto12 = require("node:crypto");
|
|
49327
|
+
var import_promises9 = require("node:fs/promises");
|
|
49328
|
+
var import_node_path9 = require("node:path");
|
|
49329
|
+
function markerFileName(ownerAddress, policyKey) {
|
|
49330
|
+
return `${(0, import_node_crypto12.createHash)("sha256").update(`${ownerAddress}
|
|
49331
|
+
${policyKey}`).digest("hex")}.json`;
|
|
49332
|
+
}
|
|
49333
|
+
var DIGITS = /^\d{1,18}$/;
|
|
49334
|
+
var HEX64 = /^[0-9a-fA-F]{64}$/;
|
|
49335
|
+
var ISO_8601 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
49336
|
+
function isRealDate(iso) {
|
|
49337
|
+
const t = Date.parse(iso);
|
|
49338
|
+
return !Number.isNaN(t) && new Date(t).toISOString().slice(0, 10) === iso.slice(0, 10);
|
|
49339
|
+
}
|
|
49340
|
+
function isMarkerShape(value) {
|
|
49341
|
+
if (typeof value !== "object" || value === null) return false;
|
|
49342
|
+
const v = value;
|
|
49343
|
+
return typeof v.policyKey === "string" && v.policyKey !== "" && typeof v.ownerAddress === "string" && v.ownerAddress !== "" && (v.operation === "CREATE" || v.operation === "UPDATE") && typeof v.nonce === "string" && DIGITS.test(v.nonce) && typeof v.txHash === "string" && HEX64.test(v.txHash) && typeof v.ceilLedgerSeq === "string" && DIGITS.test(v.ceilLedgerSeq) && typeof v.submittedAt === "string" && ISO_8601.test(v.submittedAt) && isRealDate(v.submittedAt);
|
|
49344
|
+
}
|
|
49345
|
+
var DIRECTORY_FSYNC_UNSUPPORTED = /* @__PURE__ */ new Set(["EISDIR", "EPERM", "EACCES", "EINVAL", "ENOTSUP", "ENOSYS", "EBADF"]);
|
|
49346
|
+
var errorCode = (e) => typeof e === "object" && e !== null ? e.code : void 0;
|
|
49347
|
+
function unreadable(file, why) {
|
|
49348
|
+
return new Error(
|
|
49349
|
+
`the note of an earlier self-paid send (${file}) cannot be trusted: ${why}. A send can still land for 60 ledgers: delete that file only once at least 15 minutes have passed since it was last changed and the chain is producing ledgers, or once get_my_policy shows the write`
|
|
49350
|
+
);
|
|
49351
|
+
}
|
|
49352
|
+
function createFsSelfPayMarkerStore(baseDir) {
|
|
49353
|
+
const pathFor = (owner, key) => (0, import_node_path9.join)(baseDir, markerFileName(owner, key));
|
|
49354
|
+
async function read2(ownerAddress, policyKey) {
|
|
49355
|
+
const file = pathFor(ownerAddress, policyKey);
|
|
49356
|
+
let raw;
|
|
49357
|
+
try {
|
|
49358
|
+
raw = await (0, import_promises9.readFile)(file, "utf8");
|
|
49359
|
+
} catch (e) {
|
|
49360
|
+
if (errorCode(e) === "ENOENT") return null;
|
|
49361
|
+
throw unreadable(file, `it could not be read (${errorCode(e) ?? "unknown error"})`);
|
|
49362
|
+
}
|
|
49363
|
+
let parsed;
|
|
49364
|
+
try {
|
|
49365
|
+
parsed = JSON.parse(raw);
|
|
49366
|
+
} catch {
|
|
49367
|
+
throw unreadable(file, raw === "" ? "it is empty" : "it is not valid JSON");
|
|
49368
|
+
}
|
|
49369
|
+
if (!isMarkerShape(parsed) || parsed.ownerAddress !== ownerAddress || parsed.policyKey !== policyKey) {
|
|
49370
|
+
throw unreadable(file, "it is not a valid note for this owner and key");
|
|
49371
|
+
}
|
|
49372
|
+
return parsed;
|
|
49373
|
+
}
|
|
49374
|
+
return {
|
|
49375
|
+
get: read2,
|
|
49376
|
+
async set(marker) {
|
|
49377
|
+
if (!isMarkerShape(marker)) throw new Error("refusing to save a malformed self-pay marker");
|
|
49378
|
+
await (0, import_promises9.mkdir)(baseDir, { recursive: true, mode: 448 });
|
|
49379
|
+
const filePath = pathFor(marker.ownerAddress, marker.policyKey);
|
|
49380
|
+
const tmpPath = `${filePath}.tmp-${(0, import_node_crypto12.randomUUID)()}`;
|
|
49381
|
+
try {
|
|
49382
|
+
const handle = await (0, import_promises9.open)(tmpPath, "w", 384);
|
|
49383
|
+
try {
|
|
49384
|
+
await handle.writeFile(JSON.stringify(marker, null, 2), "utf8");
|
|
49385
|
+
await handle.sync();
|
|
49386
|
+
} finally {
|
|
49387
|
+
await handle.close();
|
|
49388
|
+
}
|
|
49389
|
+
await (0, import_promises9.rename)(tmpPath, filePath);
|
|
49390
|
+
} catch (e) {
|
|
49391
|
+
await (0, import_promises9.unlink)(tmpPath).catch(() => void 0);
|
|
49392
|
+
throw e;
|
|
49393
|
+
}
|
|
49394
|
+
try {
|
|
49395
|
+
const dir = await (0, import_promises9.open)(baseDir, "r");
|
|
49396
|
+
try {
|
|
49397
|
+
await dir.sync();
|
|
49398
|
+
} finally {
|
|
49399
|
+
await dir.close();
|
|
49400
|
+
}
|
|
49401
|
+
} catch (e) {
|
|
49402
|
+
if (!DIRECTORY_FSYNC_UNSUPPORTED.has(errorCode(e) ?? "")) throw e;
|
|
49403
|
+
}
|
|
49404
|
+
},
|
|
49405
|
+
async remove(ownerAddress, policyKey, expectedTxHash) {
|
|
49406
|
+
if (expectedTxHash !== void 0) {
|
|
49407
|
+
const current = await read2(ownerAddress, policyKey);
|
|
49408
|
+
if (current === null || current.txHash !== expectedTxHash) return;
|
|
49409
|
+
}
|
|
49410
|
+
try {
|
|
49411
|
+
await (0, import_promises9.unlink)(pathFor(ownerAddress, policyKey));
|
|
49412
|
+
} catch (e) {
|
|
49413
|
+
if (errorCode(e) !== "ENOENT") throw e;
|
|
49414
|
+
}
|
|
49415
|
+
}
|
|
49416
|
+
};
|
|
49417
|
+
}
|
|
49418
|
+
|
|
48948
49419
|
// src/orchestrator/policy-affordability.ts
|
|
48949
49420
|
var NATIVE3 = "ZTX";
|
|
48950
49421
|
var MAX_DETAIL = 160;
|
|
@@ -49039,6 +49510,7 @@ async function checkAffordability(deps, accept) {
|
|
|
49039
49510
|
|
|
49040
49511
|
// src/index.ts
|
|
49041
49512
|
var packageVersion = package_default.version;
|
|
49513
|
+
var POLICY_HTTP_TIMEOUT_MS = 6e4;
|
|
49042
49514
|
function buildToolList() {
|
|
49043
49515
|
return [
|
|
49044
49516
|
{
|
|
@@ -49055,12 +49527,12 @@ function buildToolList() {
|
|
|
49055
49527
|
},
|
|
49056
49528
|
{
|
|
49057
49529
|
name: "prove_identity",
|
|
49058
|
-
description: "Answer an x401 PROOF-REQUEST and return the PROOF-RESPONSE header to replay to the resource server.",
|
|
49530
|
+
description: "Answer an x401 PROOF-REQUEST and return the PROOF-RESPONSE header to replay to the resource server. If the wallet holds several credentials, pass vc: use the one the request or the verifier's challenge names, and if it names none, prefer the Verified AI Birthcert over the Basic one and say which one you presented.",
|
|
49059
49531
|
inputSchema: {
|
|
49060
49532
|
type: "object",
|
|
49061
49533
|
properties: {
|
|
49062
49534
|
proofRequest: { type: "string", description: "The PROOF-REQUEST header value from the 401 challenge." },
|
|
49063
|
-
vc: { type: "object", description: "The VerifiableCredential to present. Omit to use the wallet's single locally-cached credential, if there is exactly one \u2014 the call fails with a clear error if none or several are cached." },
|
|
49535
|
+
vc: { type: "object", description: "The VerifiableCredential to present. Omit to use the wallet's single locally-cached credential, if there is exactly one \u2014 the call fails with a clear error if none or several are cached, so with several, pass it." },
|
|
49064
49536
|
revealAttribute: { type: "array", items: { type: "string" }, description: "Dotted disclosure paths to reveal. Omit to reveal exactly the claims the challenge (DCQL) requests; a challenge naming no claims reveals all." },
|
|
49065
49537
|
issuerKeys: {
|
|
49066
49538
|
type: "object",
|
|
@@ -49076,7 +49548,7 @@ function buildToolList() {
|
|
|
49076
49548
|
},
|
|
49077
49549
|
{
|
|
49078
49550
|
name: "create_verification_qr",
|
|
49079
|
-
description: "Create a link and a QR code that open this wallet's credential in the MyID app, so a human can see the identity behind the agent. Use it when the user asks whether the agent is verified, or asks to verify the agent in MyID. With no vc, it presents the Verified AI Birthcert if it holds one, otherwise the Basic AI Birthcert, and if it holds neither it returns created: false and says to create one first. By default it reveals only a standard minimal set (for the Verified AI Birthcert: agentName, evidenceProvider and ownerVerified; for the Basic AI Birthcert: agentUsername); pass revealAttribute to reveal other specific attributes the user asks for, and revealAll: true only when the user explicitly asks to reveal everything. A Basic AI Birthcert does not mean the owner was verified, so say so if that is what was presented. The wallet presents the credential to MBI and returns the link, the QR code image and the time the link stops working (5 minutes by default); the link holds only a reference id, and that reference id grants access to the revealed attributes until it expires. Show the link and QR code only to the human user, never pass them to another service, and tell the user to open the link in MyID (they need the app installed). If it returns created: false, relay the reason and do not invent a link.",
|
|
49551
|
+
description: "Create a link and a QR code that open this wallet's credential in the MyID app, so a human can see the identity behind the agent. Use it when the user asks whether the agent is verified, or asks to verify the agent in MyID. With no vc, it presents the Verified AI Birthcert if it holds one, otherwise the Basic AI Birthcert, and if it holds neither it returns created: false and says to create one first. By default it reveals only a standard minimal set (for the Verified AI Birthcert: agentName, evidenceProvider and ownerVerified; for the Basic AI Birthcert: agentUsername); pass revealAttribute to reveal other specific attributes the user asks for, and revealAll: true only when the user explicitly asks to reveal everything. A Basic AI Birthcert does not mean the owner was verified, so say so if that is what was presented. The wallet presents the credential to MBI and returns the link, the QR code (as an image and as a PNG file at qrCodePath) and the time the link stops working (5 minutes by default); the link holds only a reference id, and that reference id grants access to the revealed attributes until it expires. Show the link and QR code only to the human user, never pass them to another service, and tell the user to open the link in MyID (they need the app installed). In a chat channel such as WhatsApp, send the QR code by attaching the file at qrCodePath; never send any other image as the QR code, because the credential's pass design is not one, and if you cannot attach an image, give the link and say plainly that you could not send the QR code. When the credential has a saved pass design, the result also carries vcPassImagePaths: if the user wants to see the credential itself, send that image too, as its own picture captioned as the credential's pass design and never as the QR code. If it returns created: false, relay the reason and do not invent a link.",
|
|
49080
49552
|
inputSchema: {
|
|
49081
49553
|
type: "object",
|
|
49082
49554
|
properties: {
|
|
@@ -49181,7 +49653,7 @@ function buildToolList() {
|
|
|
49181
49653
|
},
|
|
49182
49654
|
{
|
|
49183
49655
|
name: "write_policy",
|
|
49184
|
-
description: 'Deploy a spending policy on chain. THIS PAYS A REAL FEE. Run policy_preflight first and show the user its `interpretation`, because a policy that is valid can still mean something other than what they asked for, and this tool cannot take that back once it is written. It only pays and writes with confirm: true, which the user must have given after seeing the interpretation and the price: without it you get the price back (state "quoted", needsConfirmation: true) and nothing is paid. Never pass confirm on your own judgement. Amounts (perTransactionMax, cumulativeMax, velocityCap) are RAW base units: 1 of a 6-decimal token is 0.000001 of it. Send the SAME attributes and the SAME amountUnit you preflighted with, and never substitute convertedAmounts for the values \u2014 that converts them twice. "whole" gives whole-token amounts the wallet converts before anything is paid; "base" confirms a raw value under one whole token is intended; without it such a value is refused. For any amount attribute you may give valueHuman (whole tokens, "100" for 100 JMYR) instead of value: the wallet converts it with the token\'s own decimals, shows both forms, and never sends valueHuman on. Never put convertedAmounts in valueHuman: those are raw base units and would be scaled again. maxTransactionCount is a count of transactions, not an amount, so give it as value and never scale it. The flow is three steps and the middle one is where care is needed: a free pre-check, a payment that writes NOTHING, and a collect that finishes the write once the payment settles. Between the payment and the settlement A PAYMENT HAS BEEN MADE and no policy exists \u2014 that window is normal, not a failure. On the sponsored path, if `state` is "settling" or "submitted", A PAYMENT HAS BEEN MADE: never call this tool again for the same policy, never tell the user it failed, and pass `paymentReceipt` to check_policy_write instead. "submitted" means the transaction is on chain but the block has not confirmed it \u2014 do NOT report the policy as created, even though a txHash is present. "written" is the only state that means the policy exists. "already_exists" and "refused" both mean nothing was paid: they come from the FREE pre-check, or, for "refused" with `policyDenied`, from Wallet BE refusing to sign. When `policyDenied` is true a spending policy refused the fee before it was signed: a decision, so do not retry, and no policy was written. When `policyCheckUnavailable` is true (state "unavailable") the spending policy check could not be completed, nothing was paid and no policy was written; trying again shortly is right. "payment_refused" is different: a payment was presented and the service rejected it, and that says nothing either way about whether the fee was taken \u2014 never tell the user they were not charged, and never tell them they were. "receipt_void" is the one state where paying again is correct \u2014 the settlement failed and the receipt bought nothing; `payFresh` is set to say so. On the sponsored path, "write_failed" and "unknown" both mean money moved and paying again would NOT help: quote `paymentReceipt` to support rather than retrying. To find a templateId, call get_policy_template_schema with no arguments \u2014 it lists the templates with their ids. Never invent one. The template contract address is never the user\'s to supply, so leave it out rather than asking. Who pays the gas is the USER\'s choice: if this returns "needs_choice", show the user both options (sponsored: a JMYR fee; self: ZTX for network gas, no fee), let them pick, and call again with gasPayer and confirm: true. Never choose for them. With gasPayer "self" there is no paymentReceipt and check_policy_write does not apply. A self-paid "submitted" means the network gas is spent: do NOT send it again, check get_my_policy, and quote the txHash to support. A self-paid "unknown" means it may be on chain: do NOT send it again, check get_my_policy, and quote the nonce. A self-paid "write_failed" means the transaction was included and rejected: the gas is spent and nothing was written. Self-paid gas is spent even when the write fails on chain. To learn the price BEFORE paying, pass dryRun: it runs every free check and returns the service\'s quote, paying nothing and writing no policy. "quoted" means that \u2014 no payment was made. It also reports `affordability`: whether this wallet currently holds the quoted amount of the fee asset (plus some ZTX for gas when the payer needs it) and whether the payment cap would refuse it \u2014 each shortfall named separately, and "unknown" when a balance could not be read, which is not a yes. Tell the user what it says before they decide to pay. A quote is what the service asked for just now; it can change, and it is not a promise the payment will be allowed \u2014 balances move and the cap is enforced again when paying. Never ask the user for their HSM password \u2014 this tool does not take one.',
|
|
49656
|
+
description: 'Deploy a spending policy on chain. THIS PAYS A REAL FEE. Run policy_preflight first and show the user its `interpretation`, because a policy that is valid can still mean something other than what they asked for, and this tool cannot take that back once it is written. It only pays and writes with confirm: true, which the user must have given after seeing the interpretation and the price: without it you get the price back (state "quoted", needsConfirmation: true) and nothing is paid. Never pass confirm on your own judgement. Amounts (perTransactionMax, cumulativeMax, velocityCap) are RAW base units: 1 of a 6-decimal token is 0.000001 of it. Send the SAME attributes and the SAME amountUnit you preflighted with, and never substitute convertedAmounts for the values \u2014 that converts them twice. "whole" gives whole-token amounts the wallet converts before anything is paid; "base" confirms a raw value under one whole token is intended; without it such a value is refused. For any amount attribute you may give valueHuman (whole tokens, "100" for 100 JMYR) instead of value: the wallet converts it with the token\'s own decimals, shows both forms, and never sends valueHuman on. Never put convertedAmounts in valueHuman: those are raw base units and would be scaled again. maxTransactionCount is a count of transactions, not an amount, so give it as value and never scale it. The flow is three steps and the middle one is where care is needed: a free pre-check, a payment that writes NOTHING, and a collect that finishes the write once the payment settles. Between the payment and the settlement A PAYMENT HAS BEEN MADE and no policy exists \u2014 that window is normal, not a failure. On the sponsored path, if `state` is "settling" or "submitted", A PAYMENT HAS BEEN MADE: never call this tool again for the same policy, never tell the user it failed, and pass `paymentReceipt` to check_policy_write instead. "submitted" means the transaction is on chain but the block has not confirmed it \u2014 do NOT report the policy as created, even though a txHash is present. "written" is the only state that means the policy exists. "already_exists" and "refused" both mean nothing was paid: they come from the FREE pre-check, or, for "refused" with `policyDenied`, from Wallet BE refusing to sign. When `policyDenied` is true a spending policy refused the fee before it was signed: a decision, so do not retry, and no policy was written. When `policyCheckUnavailable` is true (state "unavailable") the spending policy check could not be completed, nothing was paid and no policy was written; trying again shortly is right. "payment_refused" is different: a payment was presented and the service rejected it, and that says nothing either way about whether the fee was taken \u2014 never tell the user they were not charged, and never tell them they were. "receipt_void" is the one state where paying again is correct \u2014 the settlement failed and the receipt bought nothing; `payFresh` is set to say so. On the sponsored path, "write_failed" and "unknown" both mean money moved and paying again would NOT help: quote `paymentReceipt` to support rather than retrying. To find a templateId, call get_policy_template_schema with no arguments \u2014 it lists the templates with their ids. Never invent one. The template contract address is never the user\'s to supply, so leave it out rather than asking. Who pays the gas is the USER\'s choice: if this returns "needs_choice", show the user both options (sponsored: a JMYR fee; self: ZTX for network gas, no fee), let them pick, and call again with gasPayer and confirm: true. Never choose for them. With gasPayer "self" there is no paymentReceipt and check_policy_write does not apply. A self-paid "submitted" means the network gas is spent: do NOT send it again, check get_my_policy, and quote the txHash to support. A self-paid "unknown" means it may be on chain: do NOT send it again, check get_my_policy, and quote the nonce. The wallet remembers it and refuses another send, a sponsored write, a collect and a removal for that key until it has landed or can no longer land, and says when. A self-paid "write_failed" means the transaction was included and rejected: the gas is spent and nothing was written. To send a self-paid write pass maxGasRaw: the requiredRaw of the self option you showed the user. It is the most gas they agreed to, and the wallet refuses, free, if the gas now costs more, so show the user any new figure and ask again. Self-paid gas is spent even when the write fails on chain. To learn the price BEFORE paying, pass dryRun: it runs every free check and returns the service\'s quote, paying nothing and writing no policy. "quoted" means that \u2014 no payment was made. It also reports `affordability`: whether this wallet currently holds the quoted amount of the fee asset (plus some ZTX for gas when the payer needs it) and whether the payment cap would refuse it \u2014 each shortfall named separately, and "unknown" when a balance could not be read, which is not a yes. Tell the user what it says before they decide to pay. A quote is what the service asked for just now; it can change, and it is not a promise the payment will be allowed \u2014 balances move and the cap is enforced again when paying. Never ask the user for their HSM password \u2014 this tool does not take one.',
|
|
49185
49657
|
inputSchema: {
|
|
49186
49658
|
type: "object",
|
|
49187
49659
|
properties: {
|
|
@@ -49241,6 +49713,10 @@ function buildToolList() {
|
|
|
49241
49713
|
type: "string",
|
|
49242
49714
|
enum: ["sponsored", "self"],
|
|
49243
49715
|
description: 'Who pays the gas to write. "sponsored": the fee is paid in JMYR and the network gas is covered, so no ZTX is needed. "self": no fee, only the network gas in ZTX. Leave it out unless the user has chosen: when the wallet can pay the gas itself, a call without it returns state "needs_choice" with both options and what each would cost, and the user decides. Never choose for the user.'
|
|
49716
|
+
},
|
|
49717
|
+
maxGasRaw: {
|
|
49718
|
+
type: "string",
|
|
49719
|
+
description: 'Self-pay only. The most gas, in raw units (micro-ZTX), the user agreed to pay to write: the "requiredRaw" of the self option (gasOptions.self.gas.requiredRaw) that you showed them. A self-paid send needs it and is refused, free, without it or when the gas now costs more than this, so it can never cost more than the user was shown.'
|
|
49244
49720
|
}
|
|
49245
49721
|
},
|
|
49246
49722
|
required: ["policyKey", "attributes", "templateId"]
|
|
@@ -49248,7 +49724,7 @@ function buildToolList() {
|
|
|
49248
49724
|
},
|
|
49249
49725
|
{
|
|
49250
49726
|
name: "update_policy",
|
|
49251
|
-
description: 'Replace a deployed spending policy\'s attributes and validity window. THIS PAYS A REAL FEE. Read the policy first with get_my_policy and pass its `forUpdate.expectedUpdatedAtBlock` as expectedUpdatedAtBlock: if the policy has changed since, the update is refused before anything is paid. attributes is the FULL replacement set, not a patch \u2014 anything you leave out is dropped \u2014 so start from the policy\'s current attributes and change only what the user asked for. Do not pass a templateId: the policy keeps the template it already references. The validity window is kept as it is unless you pass validFromBlock or validToBlock. Run policy_preflight first and show the user its `interpretation`, because a policy that is valid can still mean something other than what they asked for, and an update cannot be taken back once it is written. It only pays and writes with confirm: true, which the user must have given after seeing the interpretation and the price: without it you get the price back (state "quoted", needsConfirmation: true) and nothing is paid. Never pass confirm on your own judgement. Amounts (perTransactionMax, cumulativeMax, velocityCap) are RAW base units, exactly as in write_policy: send the SAME attributes and the SAME amountUnit you preflighted with, and never substitute convertedAmounts for the values. For any amount attribute you may give valueHuman (whole tokens, "100" for 100 JMYR) instead of value, as in write_policy; maxTransactionCount is a count, not an amount, so give it as value and never scale it. The flow is the same three steps as write_policy: a free pre-check, a payment that changes NOTHING, and a collect that applies the update once the payment settles. Between the payment and the settlement A PAYMENT HAS BEEN MADE and the policy is still unchanged \u2014 that window is normal, not a failure. On the sponsored path, if `state` is "settling" or "submitted", A PAYMENT HAS BEEN MADE: never call this tool again for the same policy, never tell the user it failed, and pass `paymentReceipt` to check_policy_write instead. "submitted" means the transaction is on chain but the block has not confirmed it \u2014 do NOT report the policy as updated, even though a txHash is present. "written" is the only state that means the policy was updated. "not_found", "modified", "template_unavailable" and "refused" all come from the free checks, before any payment, and say so. For "modified", read the policy again with get_my_policy, show the user what it holds now, and update from that; never retry with the old expectedUpdatedAtBlock. When `policyDenied` is true a spending policy refused the fee before it was signed: a decision, so do not retry. "payment_refused" is different: a payment was presented and the service rejected it, and that says nothing either way about whether the fee was taken \u2014 never tell the user they were not charged, and never tell them they were. "receipt_void" is the one state where paying again is correct \u2014 the settlement failed and the receipt bought nothing; `payFresh` is set to say so. On the sponsored path, "write_failed" means the update was NOT applied and the policy is unchanged, but a payment may have been taken: paying again would not help, so quote `paymentReceipt` to support rather than retrying. On the sponsored path "unknown" also means money moved without a verdict: do the same. Who pays the gas is the USER\'s choice: if this returns "needs_choice", show the user both options (sponsored: a JMYR fee; self: ZTX for network gas, no fee), let them pick, and call again with gasPayer and confirm: true. Never choose for them. With gasPayer "self" there is no paymentReceipt and check_policy_write does not apply. A self-paid "submitted" means the network gas is spent: do NOT send it again, check get_my_policy, and quote the txHash to support. A self-paid "unknown" means it may be on chain: do NOT send it again, check get_my_policy, and quote the nonce. A self-paid "write_failed" means the transaction was included and rejected: the gas is spent and nothing was written. Self-paid gas is spent even when the write fails on chain. To learn the price BEFORE paying, pass dryRun: it runs every free check and returns the service\'s quote, paying nothing and changing no policy. Tell the user what it says before they decide to pay. To lift a policy\'s limits entirely, that is remove_policy, not an update with no attributes. Never ask the user for their HSM password \u2014 this tool does not take one.',
|
|
49727
|
+
description: 'Replace a deployed spending policy\'s attributes and validity window. THIS PAYS A REAL FEE. Read the policy first with get_my_policy and pass its `forUpdate.expectedUpdatedAtBlock` as expectedUpdatedAtBlock: if the policy has changed since, the update is refused before anything is paid. attributes is the FULL replacement set, not a patch \u2014 anything you leave out is dropped \u2014 so start from the policy\'s current attributes and change only what the user asked for. Do not pass a templateId: the policy keeps the template it already references. The validity window is kept as it is unless you pass validFromBlock or validToBlock. Run policy_preflight first and show the user its `interpretation`, because a policy that is valid can still mean something other than what they asked for, and an update cannot be taken back once it is written. It only pays and writes with confirm: true, which the user must have given after seeing the interpretation and the price: without it you get the price back (state "quoted", needsConfirmation: true) and nothing is paid. Never pass confirm on your own judgement. Amounts (perTransactionMax, cumulativeMax, velocityCap) are RAW base units, exactly as in write_policy: send the SAME attributes and the SAME amountUnit you preflighted with, and never substitute convertedAmounts for the values. For any amount attribute you may give valueHuman (whole tokens, "100" for 100 JMYR) instead of value, as in write_policy; maxTransactionCount is a count, not an amount, so give it as value and never scale it. The flow is the same three steps as write_policy: a free pre-check, a payment that changes NOTHING, and a collect that applies the update once the payment settles. Between the payment and the settlement A PAYMENT HAS BEEN MADE and the policy is still unchanged \u2014 that window is normal, not a failure. On the sponsored path, if `state` is "settling" or "submitted", A PAYMENT HAS BEEN MADE: never call this tool again for the same policy, never tell the user it failed, and pass `paymentReceipt` to check_policy_write instead. "submitted" means the transaction is on chain but the block has not confirmed it \u2014 do NOT report the policy as updated, even though a txHash is present. "written" is the only state that means the policy was updated. "not_found", "modified", "template_unavailable" and "refused" all come from the free checks, before any payment, and say so. For "modified", read the policy again with get_my_policy, show the user what it holds now, and update from that; never retry with the old expectedUpdatedAtBlock. When `policyDenied` is true a spending policy refused the fee before it was signed: a decision, so do not retry. "payment_refused" is different: a payment was presented and the service rejected it, and that says nothing either way about whether the fee was taken \u2014 never tell the user they were not charged, and never tell them they were. "receipt_void" is the one state where paying again is correct \u2014 the settlement failed and the receipt bought nothing; `payFresh` is set to say so. On the sponsored path, "write_failed" means the update was NOT applied and the policy is unchanged, but a payment may have been taken: paying again would not help, so quote `paymentReceipt` to support rather than retrying. On the sponsored path "unknown" also means money moved without a verdict: do the same. Who pays the gas is the USER\'s choice: if this returns "needs_choice", show the user both options (sponsored: a JMYR fee; self: ZTX for network gas, no fee), let them pick, and call again with gasPayer and confirm: true. Never choose for them. With gasPayer "self" there is no paymentReceipt and check_policy_write does not apply. A self-paid "submitted" means the network gas is spent: do NOT send it again, check get_my_policy, and quote the txHash to support. A self-paid "unknown" means it may be on chain: do NOT send it again, check get_my_policy, and quote the nonce. The wallet remembers it and refuses another send, a sponsored write, a collect and a removal for that key until it has landed or can no longer land, and says when. A self-paid "write_failed" means the transaction was included and rejected: the gas is spent and nothing was written. To send a self-paid write pass maxGasRaw: the requiredRaw of the self option you showed the user. It is the most gas they agreed to, and the wallet refuses, free, if the gas now costs more, so show the user any new figure and ask again. Self-paid gas is spent even when the write fails on chain. To learn the price BEFORE paying, pass dryRun: it runs every free check and returns the service\'s quote, paying nothing and changing no policy. Tell the user what it says before they decide to pay. To lift a policy\'s limits entirely, that is remove_policy, not an update with no attributes. Never ask the user for their HSM password \u2014 this tool does not take one.',
|
|
49252
49728
|
inputSchema: {
|
|
49253
49729
|
type: "object",
|
|
49254
49730
|
properties: {
|
|
@@ -49297,6 +49773,10 @@ function buildToolList() {
|
|
|
49297
49773
|
enum: ["sponsored", "self"],
|
|
49298
49774
|
description: 'Who pays the gas to update. "sponsored": the fee is paid in JMYR and the network gas is covered, so no ZTX is needed. "self": no fee, only the network gas in ZTX. Leave it out unless the user has chosen: when the wallet can pay the gas itself, a call without it returns state "needs_choice" with both options and what each would cost, and the user decides. Never choose for the user.'
|
|
49299
49775
|
},
|
|
49776
|
+
maxGasRaw: {
|
|
49777
|
+
type: "string",
|
|
49778
|
+
description: 'Self-pay only. The most gas, in raw units (micro-ZTX), the user agreed to pay to update: the "requiredRaw" of the self option (gasOptions.self.gas.requiredRaw) that you showed them. A self-paid send needs it and is refused, free, without it or when the gas now costs more than this, so it can never cost more than the user was shown.'
|
|
49779
|
+
},
|
|
49300
49780
|
pollBudgetMs: {
|
|
49301
49781
|
type: "number",
|
|
49302
49782
|
description: "How long to wait for the settlement before handing back the receipt. The default is about a minute; settlement usually takes around twenty seconds."
|
|
@@ -49307,7 +49787,7 @@ function buildToolList() {
|
|
|
49307
49787
|
},
|
|
49308
49788
|
{
|
|
49309
49789
|
name: "remove_policy",
|
|
49310
|
-
description: `Remove a deployed spending policy. REMOVING A POLICY REMOVES EVERY LIMIT IT SET: once it is gone, Wallet BE signs spends of that asset without any limit. Removing is free, but it needs the user's clear agreement. Without confirm: true nothing is sent and you get state "needs_confirmation" with what the policy currently limits: show the user that and what removing it means, and only if they clearly agree call again with confirm: true. Never pass confirm on your own judgement. "removed" is the only state that means the policy is gone: it is reported only when the chain no longer holds it. "submitted" means the removal is on its way but not confirmed \u2014 do NOT tell the user it is removed; check with get_my_policy (found: false means it is gone). Calling remove_policy again is free, and the service is expected not to submit a second removal. "not_found" means there was nothing to remove, or it is already gone \u2014 confirm with get_my_policy before saying which. "in_progress" means a paid write for this policy is still being completed: if this wallet holds its receipt, check_policy_write finishes it, otherwise wait and ask again later. To change a policy rather than remove it, use update_policy. Never ask the user for their HSM password \u2014 this tool does not take one.`,
|
|
49790
|
+
description: `Remove a deployed spending policy. REMOVING A POLICY REMOVES EVERY LIMIT IT SET: once it is gone, Wallet BE signs spends of that asset without any limit. Removing is free, but it needs the user's clear agreement. Without confirm: true nothing is sent and you get state "needs_confirmation" with what the policy currently limits: show the user that and what removing it means, and only if they clearly agree call again with confirm: true. Never pass confirm on your own judgement. "removed" is the only state that means the policy is gone: it is reported only when the chain no longer holds it. "submitted" means the removal is on its way but not confirmed \u2014 do NOT tell the user it is removed; check with get_my_policy (found: false means it is gone). Calling remove_policy again is free, and the service is expected not to submit a second removal. "not_found" means there was nothing to remove, or it is already gone \u2014 confirm with get_my_policy before saying which. "in_progress" means a paid write for this policy is still being completed: if this wallet holds its receipt, check_policy_write finishes it, otherwise wait and ask again later. "refused" naming a self-paid transaction means an earlier self-paid update may still land and would bring the old policy back: NOTHING WAS REMOVED; wait until the ledger it names, or read the policy with get_my_policy, then ask again. "refused" saying another write is in progress in this wallet, or "unavailable" saying a record could not be read, also means NOTHING WAS REMOVED: ask again shortly. To change a policy rather than remove it, use update_policy. Never ask the user for their HSM password \u2014 this tool does not take one.`,
|
|
49311
49791
|
inputSchema: {
|
|
49312
49792
|
type: "object",
|
|
49313
49793
|
properties: {
|
|
@@ -49329,7 +49809,7 @@ function buildToolList() {
|
|
|
49329
49809
|
},
|
|
49330
49810
|
{
|
|
49331
49811
|
name: "check_policy_write",
|
|
49332
|
-
description: 'Finish a policy write that has already been PAID FOR. FREE \u2014 this tool never pays, on any path, which is why it is the right answer to "did my policy get created?" and calling write_policy again is not. Call it with the `paymentReceipt` from a "settling" or "submitted" result, or with no arguments to resume the most recent pending write. The states mean the same as in write_policy: "written" is the only one where the policy exists, "settling" and "submitted" mean keep waiting, and "unknown" means the outcome could not be determined \u2014 none of which is a reason to pay again. A paid update_policy is finished the same way: its receipt is collected on the update route, and "written" then means the policy was updated. If this wallet holds no record of the receipt, that does NOT mean the write failed: the service completes a paid write on its own, and asking to write the same policyKey again will report the truth for free before any payment.',
|
|
49812
|
+
description: 'Finish a policy write that has already been PAID FOR. FREE \u2014 this tool never pays, on any path, which is why it is the right answer to "did my policy get created?" and calling write_policy again is not. Call it with the `paymentReceipt` from a "settling" or "submitted" result, or with no arguments to resume the most recent pending write. The states mean the same as in write_policy: "written" is the only one where the policy exists, "settling" and "submitted" mean keep waiting, and "unknown" means the outcome could not be determined \u2014 none of which is a reason to pay again. A paid update_policy is finished the same way: its receipt is collected on the update route, and "written" then means the policy was updated. "refused" or "unavailable" saying NOTHING WAS COLLECTED means a self-paid write for this policy may still land, or another write for it is in progress in this wallet: the payment is not lost, so do not pay again; ask again shortly. If this wallet holds no record of the receipt, that does NOT mean the write failed: the service completes a paid write on its own, and asking to write the same policyKey again will report the truth for free before any payment.',
|
|
49333
49813
|
inputSchema: {
|
|
49334
49814
|
type: "object",
|
|
49335
49815
|
properties: {
|
|
@@ -49418,7 +49898,7 @@ function buildToolList() {
|
|
|
49418
49898
|
},
|
|
49419
49899
|
{
|
|
49420
49900
|
name: "subscribe_and_issue",
|
|
49421
|
-
description: "Obtain a VC from MBI: build the signed payload, pay x402, and return the issued credential. If a spending policy refuses the fee, the result has `policyDenied: true` (a decision: do not retry) or `policyCheckUnavailable: true` (transient: trying again shortly is right); either way nothing was paid and no credential was issued. If a still-valid credential for this templateId is already cached locally, it is returned directly with no payment (fromCache: true). Setting forceReissue:true alone does NOT pay again \u2014 it only asks to be shown that existing credential first: you get back { issued: false, vcId, vc, fromCache: true, reason } instead, and nothing is paid. Show that existing credential to the user and ask whether they actually want to replace it; only call this tool again, with BOTH forceReissue:true and confirmReplaceExistingVc set to exactly the vcId you were shown, if they explicitly say yes. Never pass confirmReplaceExistingVc on your own judgement. When nothing valid is cached, forceReissue has no effect either way \u2014 that case has always paid and issued fresh. When a call DOES render a full credential to the user (a fresh issuance, a plain cache hit, or a confirmed replace), show every claim under vc.credentialSubject as a table \u2014 not a cherry-picked subset \u2014 labelling each key by title-casing it (the exact claim set varies by template; use schema.required/optional to know what to expect), plus vcId and validUntil if present. If vcPassImagePaths is also present, the credential's own pass-design image(s) came back attached to this result \u2014 tell the user their credential's official pass design is shown below and display the image(s); do not silently drop them from your summary just because they are not text. Payment is asset-agnostic \u2014 MBI's 402 challenge may quote the native ZETRIX token or a ZTP20 token (e.g. JMYR); pass dryRun:true first to see the quoted asset/amount for free before committing to pay. What a call actually cost is reported precisely: paidAsset/amountPaid are set ONLY when this call paid, a cache hit reports the earlier charge under originalPayment instead (never as amountPaid, so summing spend cannot double-count), and any failure after the payment has settled on chain reports paymentAttempted: { asset, amount, paymentId }. Two such failures exist and mean different things: MBI 4006 is a definitive facilitator rejection, while 4012 (HTTP 502) means the outcome is INDETERMINATE \u2014 the payment may well have landed. On 4012 the wallet automatically polls MBI's recovery endpoint and reports recovery: { status, txHash?, vcId?, polls }, where status is ISSUED (the credential exists after all \u2014 fetch it by vcId, since recovery returns no VC body), FAILED, or REQUIRED/SETTLED (still unresolved) / UNKNOWN (recovery itself unreachable). NEVER retry a payment after either failure: the funds may already be gone, and a retry charges the full amount again \u2014 look the paymentId up instead. Every response also includes { schema: { required, optional } } whenever the template schema could be read from chain \u2014 the template's full declared attribute list, so you see the complete field list, not just what went wrong. For the AI Birthcert specifically: this issues the BASIC one \u2014 self-declared by the agent, agent-paid via x402, owner identity NOT identity-verified. If the user asked for a \"verified\" AI birthcert (owner identity confirmed via MyDigital ID), use request_ai_birthcert_verification instead \u2014 this tool cannot produce that credential.",
|
|
49901
|
+
description: "Obtain a VC from MBI: build the signed payload, pay x402, and return the issued credential. If a spending policy refuses the fee, the result has `policyDenied: true` (a decision: do not retry) or `policyCheckUnavailable: true` (transient: trying again shortly is right); either way nothing was paid and no credential was issued. If a still-valid credential for this templateId is already cached locally, it is returned directly with no payment (fromCache: true). Setting forceReissue:true alone does NOT pay again \u2014 it only asks to be shown that existing credential first: you get back { issued: false, vcId, vc, fromCache: true, reason } instead, and nothing is paid. Show that existing credential to the user and ask whether they actually want to replace it; only call this tool again, with BOTH forceReissue:true and confirmReplaceExistingVc set to exactly the vcId you were shown, if they explicitly say yes. Never pass confirmReplaceExistingVc on your own judgement. When nothing valid is cached, forceReissue has no effect either way \u2014 that case has always paid and issued fresh. When a call DOES render a full credential to the user (a fresh issuance, a plain cache hit, or a confirmed replace), show every claim under vc.credentialSubject as a table \u2014 not a cherry-picked subset \u2014 labelling each key by title-casing it (the exact claim set varies by template; use schema.required/optional to know what to expect), plus vcId and validUntil if present. If vcPassImagePaths is also present, the credential's own pass-design image(s) came back attached to this result \u2014 tell the user their credential's official pass design is shown below and display the image(s); do not silently drop them from your summary just because they are not text. Payment is asset-agnostic \u2014 MBI's 402 challenge may quote the native ZETRIX token or a ZTP20 token (e.g. JMYR); pass dryRun:true first to see the quoted asset/amount for free before committing to pay. What a call actually cost is reported precisely: paidAsset/amountPaid are set ONLY when this call paid, a cache hit reports the earlier charge under originalPayment instead (never as amountPaid, so summing spend cannot double-count), and any failure after the payment has settled on chain reports paymentAttempted: { asset, amount, paymentId }. Two such failures exist and mean different things: MBI 4006 is a definitive facilitator rejection, while 4012 (HTTP 502) means the outcome is INDETERMINATE \u2014 the payment may well have landed. On 4012 the wallet automatically polls MBI's recovery endpoint and reports recovery: { status, txHash?, vcId?, polls }, where status is ISSUED (the credential exists after all \u2014 fetch it by vcId, since recovery returns no VC body), FAILED, or REQUIRED/SETTLED (still unresolved) / UNKNOWN (recovery itself unreachable). NEVER retry a payment after either failure: the funds may already be gone, and a retry charges the full amount again \u2014 look the paymentId up instead. Every response also includes { schema: { required, optional } } whenever the template schema could be read from chain \u2014 the template's full declared attribute list, so you see the complete field list, not just what went wrong. For the AI Birthcert specifically: this issues the BASIC one \u2014 self-declared by the agent, agent-paid via x402, owner identity NOT identity-verified. If the user asked for a \"verified\" AI birthcert (owner identity confirmed via MyDigital ID), use request_ai_birthcert_verification instead \u2014 this tool cannot produce that credential. If the user did not say which kind, ask first: see the `choice` note in credential_preflight.",
|
|
49422
49902
|
inputSchema: {
|
|
49423
49903
|
type: "object",
|
|
49424
49904
|
properties: {
|
|
@@ -49428,12 +49908,12 @@ function buildToolList() {
|
|
|
49428
49908
|
},
|
|
49429
49909
|
attributes: {
|
|
49430
49910
|
type: "object",
|
|
49431
|
-
description: `Claim values for the credential (schema varies by template \u2014 check what the issuer requires before guessing). ALWAYS ask the human owner directly for any name/identifier-type value (e.g. an agentUsername or similar field) \u2014 NEVER invent, guess, or silently reuse one of your own choosing; if the template requires one, get it from the user first. "agentDid" is the one exception: it does not need to be supplied, since it is auto-filled with this wallet's own holder DID (the credential's self-referential subject) unless you explicitly override it.`
|
|
49911
|
+
description: `Claim values for the credential (schema varies by template \u2014 check what the issuer requires before guessing). ALWAYS ask the human owner directly for any name/identifier-type value (e.g. an agentUsername or similar field) \u2014 NEVER invent, guess, or silently reuse one of your own choosing; if the template requires one, get it from the user first. When the user answers with a name \u2014 even a short phrase, even one that repeats words from your question \u2014 that IS the name: use it as given and do not ask for it again, unless it was refused (for example already in use). Tidying it (spaces or capitals, say) is optional and never needed; if you do want to suggest a tidier form, ask and WAIT for their answer, and use the tidied form only after a yes. Never go ahead on a name, or on a payment, that the user has not agreed to. Any example you give is only an example of the format, not a suggestion to use. "agentDid" is the one exception: it does not need to be supplied, since it is auto-filled with this wallet's own holder DID (the credential's self-referential subject) unless you explicitly override it.`
|
|
49432
49912
|
},
|
|
49433
49913
|
expirationDate: { type: "string" },
|
|
49434
49914
|
dryRun: {
|
|
49435
49915
|
type: "boolean",
|
|
49436
|
-
description: "Price the credential without paying, signing, or issuing anything. Returns { quote: { asset, maxAmountRequired, payTo, gasModel, paymentRequired? }, schema: { required, optional } }. Priced via MBI's /quote endpoint, which cannot issue \u2014 so this is safe even on a template that issues for free (such a template mints synchronously on the real path, which is why pricing never goes through it). Never writes the VC cache, so it cannot displace a credential you already hold. `quote.paymentRequired` is the authoritative free-vs-paid answer: false means issuance is currently free and `maxAmountRequired` will not be charged. When the field is ABSENT the MBI predates it and the amount is unconfirmed \u2014 report it as a possible charge, never as certainly free. Still validates required attributes locally first \u2014 a missing one blocks before any MBI call."
|
|
49916
|
+
description: "Price the credential without paying, signing, or issuing anything. Returns { quote: { asset, maxAmountRequired, payTo, gasModel, paymentRequired? }, schema: { required, optional } }. Priced via MBI's /quote endpoint, which cannot issue \u2014 so this is safe even on a template that issues for free (such a template mints synchronously on the real path, which is why pricing never goes through it). Never writes the VC cache, so it cannot displace a credential you already hold. `quote.paymentRequired` is the authoritative free-vs-paid answer: false means issuance is currently free and `maxAmountRequired` will not be charged. When the field is ABSENT the MBI predates it and the amount is unconfirmed \u2014 report it as a possible charge, never as certainly free. Tell the user in plain words that it is free or chargeable; never quote JSON field names such as paymentRequired to them. Still validates required attributes locally first \u2014 a missing one blocks before any MBI call."
|
|
49437
49917
|
},
|
|
49438
49918
|
forceReissue: {
|
|
49439
49919
|
type: "boolean",
|
|
@@ -49449,13 +49929,13 @@ function buildToolList() {
|
|
|
49449
49929
|
},
|
|
49450
49930
|
{
|
|
49451
49931
|
name: "request_ai_birthcert_verification",
|
|
49452
|
-
description: 'Start a Verified AI Birthcert issuance session with myid (MyDigital ID owner verification). ALWAYS run credential_preflight for "verified_ai_birthcert" immediately before calling this, even if you checked earlier in the conversation \u2014 preflight is free, this tool spends real funds, and a balance the user topped up a minute ago is not the balance you read before that. If a spending policy refuses the session fee, the result is { error } with `policyDenied: true` (a decision: do not retry) or `policyCheckUnavailable: true` (transient: trying again shortly is right); either way nothing was paid and no session was created. Returns { sessionId, verificationUrl, expiresAt, expiresIn, expiresInSeconds, message } \u2014 show verificationUrl to the human owner and ask them to open it and complete MyDigital ID verification (typically finishes in seconds). Tell them how long the link is good for by quoting `expiresIn` exactly as given: the wallet works it out from its own clock, so never calculate the time remaining yourself from `expiresAt` \u2014 your clock and timezone may differ from the server\'s. `message` here contains a "Tell the user:" sentence about following up \u2014 relay it rather than composing your own reassurance. Once they confirm they are done, call check_ai_birthcert_verification to see whether the credential was issued. Nothing keeps running after this call returns \u2014 there is no background polling, webhook, or timer, so you can only check status when the user sends you a new message. NEVER tell the user you have "set up an automation" or that you will "alert them" when it completes \u2014 you cannot act on your own between messages, and promising that leaves them waiting for a notification that will never come. Instead, tell them how long the link is good for and ask them to message you back once done (or after a few minutes) so you can check_ai_birthcert_verification again. ALWAYS ask the human owner directly what agentName they want to use for a NEW session \u2014 NEVER invent, guess, or silently choose one yourself. (Calling again with the SAME name to resume an already-pending session, as described below, is not "inventing" one \u2014 that is reusing the exact name the owner already gave.) IMPORTANT: agentName must be unique \u2014 if this exact name has already been used to request a Verified AI Birthcert, issuance will fail. Before calling, ask the human owner whether they want to supply any of the optional fields \u2014 agentPurpose, evidenceAssuranceLevel, ownerType, ownerVerified \u2014 do not silently omit them; they only need to say no. Calling this again with the SAME agentName while a prior session is still pending returns that same session unchanged \u2014 no new session is started and nothing is paid again. This tool spends real funds: it self-pays an x402 challenge, subject to the same credential-issuance payment cap as subscribe_and_issue, which by default allows exactly 1 JMYR per call. Set MAX_PAYMENT_AMOUNT to override it; a spending policy that governs the asset replaces the default cap. It can return { error: "..." } instead of a session. Read `message` before deciding what to tell the user or whether retrying is safe \u2014 it does NOT always mean nothing was paid. For insufficient funds or a payment-cap block specifically, nothing is created and nothing was paid, so once the underlying problem is fixed (e.g. the user tops up), calling this tool again is the right next step. Several OTHER { error } shapes mean the opposite: a payment may already have been sent on a prior or even this call (blob_already_settled, a receipt that could not be saved locally, RECEIPT VOID, OUTCOME UNKNOWN) \u2014 for any of those, calling this tool again pays the fee AGAIN, so do NOT retry without explicit agreement from the user, exactly as `message` itself will say. It can also return { settlementPending: true, paymentReceipt, message } instead \u2014 a payment WAS SENT and no session exists yet. That is NOT a failure and NOT an error, but do NOT pay again and do NOT call this tool again to retry it \u2014 call check_ai_birthcert_verification to follow it through instead. It covers three different situations, told apart by the `issuerRejected` and `paymentInvalid` fields. With neither set, do NOT assume the payment is known to have succeeded \u2014 this shape covers TWO different states that look identical from these fields alone: usually the settlement is genuinely still clearing (the message says "still being processed"), but sometimes the outcome could not be determined at all yet (the message says "has not been confirmed yet" and does not say "still being processed") \u2014 an indeterminate state, not a confirmed one, even though neither flag is set. Either way the receipt is saved and you must not pay again, so report it as "payment sent, still settling" without promising the user it definitely succeeded. When the message contains a "Tell the user:" sentence, relay that sentence rather than composing your own reassurance, and if asked what the service said, quote only what the message says it answered. With issuerRejected: true the credential service was reached and refused the request \u2014 the settlement is NOT what failed, but do NOT describe this as the payment having succeeded either: `message` quotes its reason, and you must relay that reason rather than describing it as still settling or as a success. With paymentInvalid: true the service has specifically ruled the payment or receipt invalid \u2014 do NOT say "the settlement is not what failed" for this one, since this verdict IS about the payment; relay what it said instead, and do NOT describe it as a success either. Neither this case nor issuerRejected tells you whether the fee was taken, in either direction \u2014 never tell the user they were not charged, and just as much, never tell them they WERE charged either. That no-claim rule covers issuerRejected and paymentInvalid specifically. The plain no-flag case above is different again, and NOT simply "known to have succeeded" either \u2014 see the note on it above: it is two states, only one of which is confirmed, so read `message` there too rather than assuming success from the shape alone. If the user
|
|
49932
|
+
description: 'Start a Verified AI Birthcert issuance session with myid (MyDigital ID owner verification). ALWAYS run credential_preflight for "verified_ai_birthcert" immediately before calling this, even if you checked earlier in the conversation \u2014 preflight is free, this tool spends real funds, and a balance the user topped up a minute ago is not the balance you read before that. If a spending policy refuses the session fee, the result is { error } with `policyDenied: true` (a decision: do not retry) or `policyCheckUnavailable: true` (transient: trying again shortly is right); either way nothing was paid and no session was created. Returns { sessionId, verificationUrl, expiresAt, expiresIn, expiresInSeconds, message } \u2014 show verificationUrl to the human owner and ask them to open it and complete MyDigital ID verification (typically finishes in seconds). Tell them how long the link is good for by quoting `expiresIn` exactly as given: the wallet works it out from its own clock, so never calculate the time remaining yourself from `expiresAt` \u2014 your clock and timezone may differ from the server\'s. `message` here contains a "Tell the user:" sentence about following up \u2014 relay it rather than composing your own reassurance. Once they confirm they are done, call check_ai_birthcert_verification to see whether the credential was issued. Nothing keeps running after this call returns \u2014 there is no background polling, webhook, or timer, so you can only check status when the user sends you a new message. NEVER tell the user you have "set up an automation" or that you will "alert them" when it completes \u2014 you cannot act on your own between messages, and promising that leaves them waiting for a notification that will never come. Instead, tell them how long the link is good for and ask them to message you back once done (or after a few minutes) so you can check_ai_birthcert_verification again. ALWAYS ask the human owner directly what agentName they want to use for a NEW session \u2014 NEVER invent, guess, or silently choose one yourself. (Calling again with the SAME name to resume an already-pending session, as described below, is not "inventing" one \u2014 that is reusing the exact name the owner already gave.) IMPORTANT: agentName must be unique \u2014 if this exact name has already been used to request a Verified AI Birthcert, issuance will fail. Before calling, ask the human owner whether they want to supply any of the optional fields \u2014 agentPurpose, evidenceAssuranceLevel, ownerType, ownerVerified \u2014 do not silently omit them; they only need to say no. Calling this again with the SAME agentName while a prior session is still pending returns that same session unchanged \u2014 no new session is started and nothing is paid again. This tool spends real funds: it self-pays an x402 challenge, subject to the same credential-issuance payment cap as subscribe_and_issue, which by default allows exactly 1 JMYR per call. Set MAX_PAYMENT_AMOUNT to override it; a spending policy that governs the asset replaces the default cap. It can return { error: "..." } instead of a session. Read `message` before deciding what to tell the user or whether retrying is safe \u2014 it does NOT always mean nothing was paid. For insufficient funds or a payment-cap block specifically, nothing is created and nothing was paid, so once the underlying problem is fixed (e.g. the user tops up), calling this tool again is the right next step. Several OTHER { error } shapes mean the opposite: a payment may already have been sent on a prior or even this call (blob_already_settled, a receipt that could not be saved locally, RECEIPT VOID, OUTCOME UNKNOWN) \u2014 for any of those, calling this tool again pays the fee AGAIN, so do NOT retry without explicit agreement from the user, exactly as `message` itself will say. It can also return { settlementPending: true, paymentReceipt, message } instead \u2014 a payment WAS SENT and no session exists yet. That is NOT a failure and NOT an error, but do NOT pay again and do NOT call this tool again to retry it \u2014 call check_ai_birthcert_verification to follow it through instead. It covers three different situations, told apart by the `issuerRejected` and `paymentInvalid` fields. With neither set, do NOT assume the payment is known to have succeeded \u2014 this shape covers TWO different states that look identical from these fields alone: usually the settlement is genuinely still clearing (the message says "still being processed"), but sometimes the outcome could not be determined at all yet (the message says "has not been confirmed yet" and does not say "still being processed") \u2014 an indeterminate state, not a confirmed one, even though neither flag is set. Either way the receipt is saved and you must not pay again, so report it as "payment sent, still settling" without promising the user it definitely succeeded. When the message contains a "Tell the user:" sentence, relay that sentence rather than composing your own reassurance, and if asked what the service said, quote only what the message says it answered. With issuerRejected: true the credential service was reached and refused the request \u2014 the settlement is NOT what failed, but do NOT describe this as the payment having succeeded either: `message` quotes its reason, and you must relay that reason rather than describing it as still settling or as a success. With paymentInvalid: true the service has specifically ruled the payment or receipt invalid \u2014 do NOT say "the settlement is not what failed" for this one, since this verdict IS about the payment; relay what it said instead, and do NOT describe it as a success either. Neither this case nor issuerRejected tells you whether the fee was taken, in either direction \u2014 never tell the user they were not charged, and just as much, never tell them they WERE charged either. That no-claim rule covers issuerRejected and paymentInvalid specifically. The plain no-flag case above is different again, and NOT simply "known to have succeeded" either \u2014 see the note on it above: it is two states, only one of which is confirmed, so read `message` there too rather than assuming success from the shape alone. If the request does not say which kind of AI Birthcert the user wants, do not start this: run credential_preflight for both kinds and ask which they prefer (see its `choice` note), and use this tool only for the Verified kind; if they want the self-declared, non-verified Basic one, use subscribe_and_issue for that. BEFORE this pays or starts a session, it checks whether this holder already has a Verified AI Birthcert \u2014 found in the local cache, or (if the cache has not seen it yet) resolved from a previously-issued session. If one exists and is STILL VALID, nothing is paid and no session is started \u2014 you get back { existingVerifiedVc: { vcId, validUntil }, message } instead. Show that existing credential to the user and ask whether they actually want to replace it; only call this tool again, with confirmReplaceExistingVc set to exactly existingVerifiedVc.vcId, if they explicitly say yes. Never pass confirmReplaceExistingVc on your own judgement. If the existing VC has already EXPIRED, this proceeds automatically \u2014 no confirmation needed \u2014 and the result carries replacedExpiredVc: { vcId, validUntil } naming the one it replaced. That field can appear on a settled session, on a payment still settling, or on an EARLIER call\'s still-pending session simply being returned unchanged \u2014 so treat it only as "the holder\'s old VC had expired", never as proof that THIS call itself just spent money; read the rest of the result (an error, a settled session, or settlementPending) to know what this call actually did. It can also return { error } specifically because an already-issued VC exists but could not be confirmed as valid or expired (a resolution problem, not a payment problem) \u2014 nothing is paid on that path either; relay `error` as given, since it names the concrete next step (retry check_ai_birthcert_verification, or as a last resort clear_stuck_payment_receipt with the user\'s explicit agreement).',
|
|
49453
49933
|
inputSchema: {
|
|
49454
49934
|
type: "object",
|
|
49455
49935
|
properties: {
|
|
49456
49936
|
agentName: {
|
|
49457
49937
|
type: "string",
|
|
49458
|
-
description: "A unique, human-readable name for this agent. Must not already be in use for a Verified AI Birthcert, or issuance will fail. ALWAYS ask the human owner directly which name to use for a new session \u2014 NEVER invent, guess, or silently choose one yourself."
|
|
49938
|
+
description: "A unique, human-readable name for this agent. Must not already be in use for a Verified AI Birthcert, or issuance will fail. ALWAYS ask the human owner directly which name to use for a new session \u2014 NEVER invent, guess, or silently choose one yourself. When the user answers with a name \u2014 even a short phrase, even one that repeats words from your question \u2014 that IS the name: use it as given and do not ask for it again, unless it was refused (for example already in use). Tidying it (spaces or capitals, say) is optional and never needed; if you do want to suggest a tidier form, ask and WAIT for their answer, and use the tidied form only after a yes. Never go ahead on a name, or on a payment, that the user has not agreed to. Any example you give is only an example of the format, not a suggestion to use."
|
|
49459
49939
|
},
|
|
49460
49940
|
agentPurpose: { type: "string", description: 'Optional \u2014 what this agent does, e.g. "Negotiate and settle supplier invoices".' },
|
|
49461
49941
|
evidenceAssuranceLevel: { type: "string", description: 'Optional \u2014 assurance level of the identity evidence, e.g. "high".' },
|
|
@@ -49484,13 +49964,13 @@ function buildToolList() {
|
|
|
49484
49964
|
},
|
|
49485
49965
|
{
|
|
49486
49966
|
name: "credential_preflight",
|
|
49487
|
-
description: "FREE readiness check \u2014 call this FIRST, before collecting ANY application detail from the user, whenever they ask for a credential. Spends nothing, signs no transaction, creates no session. Returns { ready, fee, balances, cap, schema?, alreadyHeld?, blockers, notChecked }: the live fee and which side pays gas, the balances that matter, whether the spending limit permits it, and \u2014 for a template credential \u2014 the attributes it requires. `blockers` lists EVERY reason it is not ready at once (a low balance and a too-low spending limit are different problems and both appear together), so one round of fixes is enough rather than discovering them one failed payment at a time. ALWAYS relay `notChecked` too: a clean result is not a guarantee. In particular it does NOT check whether an agent name is free \u2014 myid decides that at issuance, after payment. Report `fee.display` and each balance's `display` to the user, never the raw base-unit numbers. `fee.paymentRequired: false` means issuance is currently FREE \u2014 `fee.display` is then only what it WOULD cost if payment were switched back on, so do not ask the user to fund it and do not present that amount as a charge.
|
|
49967
|
+
description: "FREE readiness check \u2014 call this FIRST, before collecting ANY application detail from the user, whenever they ask for a credential. Spends nothing, signs no transaction, creates no session. Returns { ready, fee, balances, cap, schema?, alreadyHeld?, blockers, notChecked }: the live fee and which side pays gas, the balances that matter, whether the spending limit permits it, and \u2014 for a template credential \u2014 the attributes it requires. `blockers` lists EVERY reason it is not ready at once (a low balance and a too-low spending limit are different problems and both appear together), so one round of fixes is enough rather than discovering them one failed payment at a time. ALWAYS relay `notChecked` too: a clean result is not a guarantee. In particular it does NOT check whether an agent name is free \u2014 myid decides that at issuance, after payment. Report `fee.display` and each balance's `display` to the user, never the raw base-unit numbers. `fee.paymentRequired: false` means issuance is currently FREE \u2014 `fee.display` is then only what it WOULD cost if payment were switched back on, so do not ask the user to fund it and do not present that amount as a charge. Tell the user in plain words that it is free or chargeable; never quote JSON field names such as paymentRequired to them. A free credential sends no transaction, so no gas is spent and a missing ZTX balance is not a blocker on it: never ask the user to fund ZTX for a free credential, and if notChecked mentions ZTX, pass it on as a note only. When the field is ABSENT the cost is unknown (an older MBI), and it is treated as chargeable \u2014 never report absent as free. If the result has `alreadyHeld`, this wallet already holds a valid copy of that credential: tell the user so, name it, and stop \u2014 do not ask for an agent name or any other detail, and do not begin an issuance. A new one would replace it. That stop applies to that credential only: the `choice` note still applies to the other kind. Only when the user has explicitly said they want a replacement, run this check with replacing: true. When a user asks for an AI Birthcert and the request does not say which kind they want (Basic, which is self-declared, or Verified, through MyDigital ID), do not pick one: follow the `choice` note in the result, which is to run this check for both kinds, tell the user the difference in plain words, and ask which they prefer.",
|
|
49488
49968
|
inputSchema: {
|
|
49489
49969
|
type: "object",
|
|
49490
49970
|
properties: {
|
|
49491
49971
|
credential: {
|
|
49492
49972
|
type: "string",
|
|
49493
|
-
description: 'Which credential to price: "verified_ai_birthcert" for the MyDigital-ID-verified AI Birthcert, or a template id (did:zid:...) / known template name
|
|
49973
|
+
description: 'Which credential to price: "verified_ai_birthcert" for the VERIFIED (MyDigital-ID-verified) AI Birthcert, or a template id (did:zid:...) / known template name for a template-issued one \u2014 "AI Birthcert" is the BASIC, self-declared one. For the Verified one use exactly "verified_ai_birthcert": any name containing "birthcert" is priced as the Basic one.'
|
|
49494
49974
|
},
|
|
49495
49975
|
agentName: {
|
|
49496
49976
|
type: "string",
|
|
@@ -49566,10 +50046,10 @@ function buildPolicyWriteDeps(input) {
|
|
|
49566
50046
|
const templateContract = input.policyTemplateAddress;
|
|
49567
50047
|
return {
|
|
49568
50048
|
policyWriteDeps: {
|
|
49569
|
-
...input.policyWriteUrl ? { client: new PolicyWriteClient(input.policyWriteUrl, (url, init) => fetch(url, init)) } : {},
|
|
50049
|
+
...input.policyWriteUrl ? { client: new PolicyWriteClient(input.policyWriteUrl, (url, init) => fetch(url, { ...init, signal: AbortSignal.timeout(POLICY_HTTP_TIMEOUT_MS) })) } : {},
|
|
49570
50050
|
// A self-paid write has no service to catch what the attribute vocabulary would have, so it needs a vocabulary to check against.
|
|
49571
50051
|
hasVocabularySource: Boolean(input.readVocabulary),
|
|
49572
|
-
receipts: createFsPolicyWriteReceiptStore((0,
|
|
50052
|
+
receipts: createFsPolicyWriteReceiptStore((0, import_node_path10.join)(input.stateDir, "policy-write-receipts")),
|
|
49573
50053
|
// The one narrowing left, and it is here rather than at the call site so the call site
|
|
49574
50054
|
// cannot pass something else entirely. The orchestrator describes an accept as an opaque
|
|
49575
50055
|
// record because it never reads one; the payer wants the SDK's type.
|
|
@@ -49590,7 +50070,10 @@ function buildPolicyWriteDeps(input) {
|
|
|
49590
50070
|
selfPay: createSelfPay({
|
|
49591
50071
|
registryAddress: input.policyRegistryAddress,
|
|
49592
50072
|
ownerAddress: input.ownerAddress,
|
|
49593
|
-
...input.selfPayChain
|
|
50073
|
+
...input.selfPayChain,
|
|
50074
|
+
// One note per (owner, policy key), written before a send and kept until its outcome is settled.
|
|
50075
|
+
// Scoped by network: the same key gives the same address on testnet and mainnet, and a testnet note must not block a mainnet write.
|
|
50076
|
+
markers: createFsSelfPayMarkerStore((0, import_node_path10.join)(input.stateDir, "self-pay-markers", input.network.replace(/[^A-Za-z0-9_-]/g, "-")))
|
|
49594
50077
|
})
|
|
49595
50078
|
} : {},
|
|
49596
50079
|
checkAffordability: (accept) => checkAffordability({ queryBalance: input.queryBalance, caps: input.caps, policyGoverns: input.policyGoverns }, accept),
|
|
@@ -49712,8 +50195,8 @@ function asPayRequest(accept) {
|
|
|
49712
50195
|
}
|
|
49713
50196
|
async function main() {
|
|
49714
50197
|
const fileEnv = loadConfigFileEnv(process.argv, (p) => (0, import_node_fs.readFileSync)(p, "utf8"));
|
|
49715
|
-
const stateDir = (process.env.ZETRIX_WALLET_STATE_DIR ?? fileEnv.ZETRIX_WALLET_STATE_DIR ?? (0,
|
|
49716
|
-
const accountStore = createFsAccountStore((0,
|
|
50198
|
+
const stateDir = (process.env.ZETRIX_WALLET_STATE_DIR ?? fileEnv.ZETRIX_WALLET_STATE_DIR ?? (0, import_node_path10.join)((0, import_node_os2.homedir)(), ".agentic-wallet-mcp")).replace(/\/+$/, "");
|
|
50199
|
+
const accountStore = createFsAccountStore((0, import_node_path10.join)(stateDir, "account.json"));
|
|
49717
50200
|
if (process.argv[2] === "export-credentials") {
|
|
49718
50201
|
const { exitCode } = await exportCredentials({
|
|
49719
50202
|
getAccount: () => accountStore.get(),
|
|
@@ -49733,7 +50216,7 @@ async function main() {
|
|
|
49733
50216
|
const config2 = loadConfig(env);
|
|
49734
50217
|
const hsmPassword = config2.hsmPassword;
|
|
49735
50218
|
const be = new WalletBeClient(config2.walletBeUrl);
|
|
49736
|
-
const sleep = (ms) => new Promise((
|
|
50219
|
+
const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
49737
50220
|
const { zetrixAddress, holderDid, publicKeyHex, created, didMismatch, activated } = await resolveHolder(
|
|
49738
50221
|
{
|
|
49739
50222
|
createAccount: (password) => be.createAccount(password),
|
|
@@ -49786,8 +50269,8 @@ async function main() {
|
|
|
49786
50269
|
const nodeBaseUrl = `https://${config2.nodeHost}${config2.nodePort ? `:${config2.nodePort}` : ""}`;
|
|
49787
50270
|
const nodeMetaQuery = (url) => fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
|
|
49788
50271
|
const resolveTemplateFields = (templateId) => fetchTemplateFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
|
|
49789
|
-
const cacheScope = (0,
|
|
49790
|
-
const vcCache = createFsVcCache((0,
|
|
50272
|
+
const cacheScope = (0, import_node_crypto13.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
|
|
50273
|
+
const vcCache = createFsVcCache((0, import_node_path10.join)(config2.stateDir, "vc-cache", cacheScope));
|
|
49791
50274
|
const mbi = new MbiClient(config2.mbiBaseUrl);
|
|
49792
50275
|
const messageSigner = (message) => be.signMessage(message, zetrixAddress, hsmPassword);
|
|
49793
50276
|
const formatAssetAmount = (asset, raw) => describeAssetAmount(asset, raw, contractQuery);
|
|
@@ -49839,10 +50322,12 @@ async function main() {
|
|
|
49839
50322
|
}
|
|
49840
50323
|
};
|
|
49841
50324
|
const { pay, payForCredential, payForPolicyWrite, preflightCaps, policyWriteCaps } = buildPayers(config2, makePay);
|
|
49842
|
-
const downloadQuarantine = createFsDownloadQuarantineStore((0,
|
|
49843
|
-
const passImagesDir = (0,
|
|
50325
|
+
const downloadQuarantine = createFsDownloadQuarantineStore((0, import_node_path10.join)(config2.stateDir, "ssivc-download-quarantine"));
|
|
50326
|
+
const passImagesDir = (0, import_node_path10.join)(config2.stateDir, "vc-pass-images");
|
|
50327
|
+
const verificationQrFiles = createFsQrFileStore((0, import_node_path10.join)(config2.stateDir, "verification-qr"));
|
|
50328
|
+
void verificationQrFiles.sweep().catch(() => void 0);
|
|
49844
50329
|
const verifyAiBirthcert = config2.ssivcBaseUrl ? (() => {
|
|
49845
|
-
const ssivcSessionStore = createFsSsivcSessionStore((0,
|
|
50330
|
+
const ssivcSessionStore = createFsSsivcSessionStore((0, import_node_path10.join)(config2.stateDir, "ssivc-session.json"));
|
|
49846
50331
|
const ssivc = new SsivcClient(config2.ssivcBaseUrl);
|
|
49847
50332
|
const verifyAiBirthcertDeps = {
|
|
49848
50333
|
ssivc,
|
|
@@ -49979,6 +50464,16 @@ async function main() {
|
|
|
49979
50464
|
// The same submit wrapper transfer_token uses, so a node refusal is told apart from an answer that never came.
|
|
49980
50465
|
submit: transferDeps.submit,
|
|
49981
50466
|
queryNativeBalance: () => queryTokenBalance2("ZTX"),
|
|
50467
|
+
// The current ledger, so a self-paid transaction can carry an expiry. `undefined` when it cannot be read.
|
|
50468
|
+
currentLedger: async () => {
|
|
50469
|
+
try {
|
|
50470
|
+
const r = await sdk.block.getNumber();
|
|
50471
|
+
const n = r.errorCode === 0 ? r.result?.header?.blockNumber : void 0;
|
|
50472
|
+
return n === void 0 ? void 0 : String(n);
|
|
50473
|
+
} catch {
|
|
50474
|
+
return void 0;
|
|
50475
|
+
}
|
|
50476
|
+
},
|
|
49982
50477
|
// What became of a submitted transaction, so one that was included and then rejected by the contract (the gas is still spent)
|
|
49983
50478
|
// is reported as that.
|
|
49984
50479
|
getTransaction: buildTransactionLookup((hash) => sdk.transaction.getInfo(hash))
|
|
@@ -50006,7 +50501,9 @@ async function main() {
|
|
|
50006
50501
|
signHexBlob: walletBeSignerFn,
|
|
50007
50502
|
signMessage: messageSigner,
|
|
50008
50503
|
linkTemplate: config2.myidVerifyLinkTemplate,
|
|
50009
|
-
renderQr: renderQrPng
|
|
50504
|
+
renderQr: renderQrPng,
|
|
50505
|
+
// A chat channel (WhatsApp) sends files, not MCP image blocks: without a file the agent has no QR to attach.
|
|
50506
|
+
saveQr: (png, referenceId, expiresAtMs) => verificationQrFiles.save(png, referenceId, expiresAtMs)
|
|
50010
50507
|
}),
|
|
50011
50508
|
verifyAiBirthcert,
|
|
50012
50509
|
// So credential_preflight can say "you already have one" before the agent asks the user for anything.
|
|
@@ -50025,7 +50522,7 @@ async function main() {
|
|
|
50025
50522
|
if (!fn) throw new Error(`Unknown tool: ${name}`);
|
|
50026
50523
|
try {
|
|
50027
50524
|
const result = await fn(args ?? {});
|
|
50028
|
-
return { content: await buildToolContent(result, (p) => (0,
|
|
50525
|
+
return { content: await buildToolContent(result, (p) => (0, import_promises10.readFile)(p)) };
|
|
50029
50526
|
} catch (err) {
|
|
50030
50527
|
const chain = [];
|
|
50031
50528
|
let e = err;
|