@mida-context/sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +199 -51
- package/dist/types/api/chain-views.d.ts +35 -15
- package/dist/types/protocol/errors.d.ts +71 -0
- package/dist/types/sdk/batched.d.ts +34 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -230,6 +230,11 @@ var MidaError = class extends Error {
|
|
|
230
230
|
function isMidaError(value, code) {
|
|
231
231
|
return value instanceof MidaError && (code === void 0 || value.code === code);
|
|
232
232
|
}
|
|
233
|
+
var ACCEPTABLE_AGENT_NAME_CHARS = new RegExp(
|
|
234
|
+
"^[\\p{L}\\p{N}][\\p{L}\\p{M}\\p{N} ._\\-\\u200c\\u200d]*$",
|
|
235
|
+
"u"
|
|
236
|
+
);
|
|
237
|
+
var STACKED_MARK_RUN = new RegExp("\\p{M}{5}", "u");
|
|
233
238
|
|
|
234
239
|
// packages/protocol/src/constants.ts
|
|
235
240
|
var PERMISSION = { READ: 1, CREATE: 2, SUPERSEDE_OWN: 4, SUPERSEDE_ANY: 8 };
|
|
@@ -4987,6 +4992,38 @@ var REJECT_NAMES = new Map(Object.entries(BATCH_REJECT).map(([name, code]) => [c
|
|
|
4987
4992
|
import { zeroHash as zeroHash6 } from "viem";
|
|
4988
4993
|
var lower = (value) => value.toLowerCase();
|
|
4989
4994
|
var RECORDS_PER_MULTICALL = 200;
|
|
4995
|
+
var multicall3Probes = /* @__PURE__ */ new Map();
|
|
4996
|
+
var multicall3AbsentSink;
|
|
4997
|
+
var multicall3AbsentSent = /* @__PURE__ */ new Set();
|
|
4998
|
+
function multicall3ProbeEntry(context) {
|
|
4999
|
+
const key = `${context.deployment.chainId}:${MULTICALL3_ADDRESS}`;
|
|
5000
|
+
const held = multicall3Probes.get(key);
|
|
5001
|
+
if (held !== void 0) return held;
|
|
5002
|
+
const entry = {
|
|
5003
|
+
size: void 0,
|
|
5004
|
+
// Promise.resolve().then(...) so a client without getCode — a custom transport, a test rig —
|
|
5005
|
+
// throws INSIDE the chain and lands on the same eviction as an RPC that refuses the call.
|
|
5006
|
+
probe: Promise.resolve().then(() => context.publicClient.getCode({ address: MULTICALL3_ADDRESS })).then((code) => {
|
|
5007
|
+
const absent = code === void 0 || code === "0x";
|
|
5008
|
+
if (absent && multicall3AbsentSink !== void 0 && !multicall3AbsentSent.has(context.deployment.chainId)) {
|
|
5009
|
+
multicall3AbsentSent.add(context.deployment.chainId);
|
|
5010
|
+
try {
|
|
5011
|
+
multicall3AbsentSink(context.deployment.chainId);
|
|
5012
|
+
} catch {
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
return entry.size = absent ? 1 : RECORDS_PER_MULTICALL;
|
|
5016
|
+
})
|
|
5017
|
+
};
|
|
5018
|
+
entry.probe.catch(() => {
|
|
5019
|
+
if (multicall3Probes.get(key) === entry) multicall3Probes.delete(key);
|
|
5020
|
+
});
|
|
5021
|
+
multicall3Probes.set(key, entry);
|
|
5022
|
+
return entry;
|
|
5023
|
+
}
|
|
5024
|
+
function sharedMulticall3Probe(context) {
|
|
5025
|
+
return multicall3ProbeEntry(context).probe;
|
|
5026
|
+
}
|
|
4990
5027
|
var RegistryReader = class {
|
|
4991
5028
|
constructor(context) {
|
|
4992
5029
|
this.context = context;
|
|
@@ -4995,22 +5032,24 @@ var RegistryReader = class {
|
|
|
4995
5032
|
#recordBatchSize;
|
|
4996
5033
|
#recordBatchSizeProbe;
|
|
4997
5034
|
/**
|
|
4998
|
-
* The probed batch size once known, undefined while unprobed. Lets a BudgetedReader
|
|
4999
|
-
*
|
|
5035
|
+
* The probed batch size once known, undefined while unprobed. Lets a BudgetedReader answer
|
|
5036
|
+
* without charging the request — but "unprobed" does not mean "no read": on a cold cache
|
|
5037
|
+
* `multicall3ProbeEntry` starts the `getCode` probe right here, the chain answers it in the
|
|
5038
|
+
* background, and the getter reports undefined until that answer lands (in-38 V-3).
|
|
5000
5039
|
*/
|
|
5001
5040
|
get knownRecordBatchSize() {
|
|
5002
|
-
return this.#recordBatchSize;
|
|
5041
|
+
return this.#recordBatchSize ?? multicall3ProbeEntry(this.context).size;
|
|
5003
5042
|
}
|
|
5004
5043
|
/**
|
|
5005
5044
|
* The most contextIds one `getRecords` call answers in a single chain read: RECORDS_PER_MULTICALL
|
|
5006
|
-
* when the chain carries Multicall3, 1 where it does not. Probed once per
|
|
5007
|
-
* and cached; a chain that cannot answer the
|
|
5008
|
-
* probe
|
|
5009
|
-
* not serve `eth_getCode`.
|
|
5045
|
+
* when the chain carries Multicall3, 1 where it does not. Probed once per PROCESS with `getCode`
|
|
5046
|
+
* — see `multicall3Probes` — and cached per reader once answered; a chain that cannot answer the
|
|
5047
|
+
* probe cannot run a multicall either, so a failed probe resolves to 1 — the per-row path —
|
|
5048
|
+
* rather than breaking reads on an RPC that does not serve `eth_getCode`.
|
|
5010
5049
|
*/
|
|
5011
5050
|
recordBatchSize() {
|
|
5012
5051
|
if (this.#recordBatchSize !== void 0) return Promise.resolve(this.#recordBatchSize);
|
|
5013
|
-
this.#recordBatchSizeProbe ??=
|
|
5052
|
+
this.#recordBatchSizeProbe ??= multicall3ProbeEntry(this.context).probe.then((size) => this.#recordBatchSize = size).catch(() => this.#recordBatchSize = 1);
|
|
5014
5053
|
return this.#recordBatchSizeProbe;
|
|
5015
5054
|
}
|
|
5016
5055
|
now() {
|
|
@@ -5077,43 +5116,54 @@ var RegistryReader = class {
|
|
|
5077
5116
|
}
|
|
5078
5117
|
}
|
|
5079
5118
|
/**
|
|
5080
|
-
* `getRecord` for a batch of contextIds. With Multicall3
|
|
5081
|
-
* never-anchored uploads can no longer spend a request's
|
|
5082
|
-
* `ContextNotFound` revert of an orphaned upload comes back as
|
|
5083
|
-
* never an error — and it is the ONLY failure that does: an
|
|
5084
|
-
* any other revert throws exactly as `getRecord` throws, so
|
|
5085
|
-
* silently dropped a real record. Chains without Multicall3
|
|
5086
|
-
* unchanged.
|
|
5087
|
-
*
|
|
5088
|
-
*
|
|
5119
|
+
* `getRecord` for a batch of contextIds. With Multicall3 each `recordBatchSize()`-sized chunk of
|
|
5120
|
+
* the ids is ONE `eth_call` — hundreds of never-anchored uploads can no longer spend a request's
|
|
5121
|
+
* read budget a row at a time. The `ContextNotFound` revert of an orphaned upload comes back as
|
|
5122
|
+
* a null entry — "not anchored", never an error — and it is the ONLY failure that does: an
|
|
5123
|
+
* out-of-gas, an undecodable result or any other revert throws exactly as `getRecord` throws, so
|
|
5124
|
+
* a caller can never read a list that silently dropped a real record. Chains without Multicall3
|
|
5125
|
+
* fall back to one `getRecord` per id, unchanged.
|
|
5126
|
+
* Chunks run in order and a failing chunk fails the whole call. Callers that pass more than
|
|
5127
|
+
* `recordBatchSize()` ids pay one aggregate call per chunk — the budgeted wrapper slices to the
|
|
5128
|
+
* batch size itself, so inside a request each call still costs exactly one unit of read budget
|
|
5129
|
+
* (or `contextIds.length` units on the per-row path, identical to `getRecord`).
|
|
5089
5130
|
*/
|
|
5090
5131
|
async getRecords(contextIds) {
|
|
5091
5132
|
if (contextIds.length === 0) return [];
|
|
5092
|
-
|
|
5133
|
+
const batchSize = await this.recordBatchSize();
|
|
5134
|
+
if (batchSize === 1) {
|
|
5093
5135
|
return Promise.all(contextIds.map((contextId2) => this.getRecord(contextId2)));
|
|
5094
5136
|
}
|
|
5095
|
-
const
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5137
|
+
const records = [];
|
|
5138
|
+
for (let offset = 0; offset < contextIds.length; offset += batchSize) {
|
|
5139
|
+
const results = await this.context.publicClient.multicall({
|
|
5140
|
+
// batchSize 0 disables viem's calldata chunking (default 1024 bytes): the whole batch is a
|
|
5141
|
+
// single aggregate3 eth_call, which is what one unit of read budget pays for.
|
|
5142
|
+
batchSize: 0,
|
|
5143
|
+
multicallAddress: MULTICALL3_ADDRESS,
|
|
5144
|
+
allowFailure: true,
|
|
5145
|
+
contracts: contextIds.slice(offset, offset + batchSize).map((contextId2) => ({
|
|
5146
|
+
address: this.context.deployment.contextRegistry,
|
|
5147
|
+
abi: contextRegistryAbi,
|
|
5148
|
+
functionName: "getRecord",
|
|
5149
|
+
args: [contextId2]
|
|
5150
|
+
}))
|
|
5151
|
+
});
|
|
5152
|
+
for (const entry of results) {
|
|
5153
|
+
if (entry.status === "success") {
|
|
5154
|
+
const record = entry.result;
|
|
5155
|
+
records.push({ ...record, owner: lower(record.owner) });
|
|
5156
|
+
continue;
|
|
5157
|
+
}
|
|
5158
|
+
const mapped = toMidaError(entry.error);
|
|
5159
|
+
if (isMidaError(mapped, "NOT_FOUND")) {
|
|
5160
|
+
records.push(null);
|
|
5161
|
+
continue;
|
|
5162
|
+
}
|
|
5163
|
+
throw mapped;
|
|
5112
5164
|
}
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
throw mapped;
|
|
5116
|
-
});
|
|
5165
|
+
}
|
|
5166
|
+
return records;
|
|
5117
5167
|
}
|
|
5118
5168
|
async #capability(functionName, args) {
|
|
5119
5169
|
try {
|
|
@@ -5387,6 +5437,79 @@ function requireAnchor(deployment) {
|
|
|
5387
5437
|
}
|
|
5388
5438
|
return batchAnchor;
|
|
5389
5439
|
}
|
|
5440
|
+
var lowerAddress = (value) => value.toLowerCase();
|
|
5441
|
+
async function prefetchBatchedLookups(input) {
|
|
5442
|
+
const batchAnchor = input.deployment.batchAnchor;
|
|
5443
|
+
if (batchAnchor === void 0) return void 0;
|
|
5444
|
+
const batchSize = await sharedMulticall3Probe({ publicClient: input.client, deployment: input.deployment }).catch(() => 1);
|
|
5445
|
+
if (batchSize === 1) return void 0;
|
|
5446
|
+
const owner = input.owner.toLowerCase();
|
|
5447
|
+
const namespaceId2 = input.namespaceId.toLowerCase();
|
|
5448
|
+
const batchIds = /* @__PURE__ */ new Set();
|
|
5449
|
+
const lineageIds = /* @__PURE__ */ new Set();
|
|
5450
|
+
const signers = /* @__PURE__ */ new Set();
|
|
5451
|
+
for (const item of input.items) {
|
|
5452
|
+
const wire2 = item.save.message;
|
|
5453
|
+
if (wire2.owner.toLowerCase() !== owner || wire2.namespaceId.toLowerCase() !== namespaceId2) continue;
|
|
5454
|
+
if (item.state === "ANCHORED") {
|
|
5455
|
+
if (item.batchId !== void 0) batchIds.add(item.batchId.toLowerCase());
|
|
5456
|
+
if (item.lineageId !== void 0) lineageIds.add(item.lineageId.toLowerCase());
|
|
5457
|
+
} else if (item.state !== "QUEUED" && item.state !== "SUBMITTED") {
|
|
5458
|
+
continue;
|
|
5459
|
+
}
|
|
5460
|
+
try {
|
|
5461
|
+
signers.add(
|
|
5462
|
+
lowerAddress(
|
|
5463
|
+
await recoverTypedDataAddress2({
|
|
5464
|
+
...batchSaveTypedData({ chainId: input.chainId, batchAnchor, message: wireMessage(wire2) }),
|
|
5465
|
+
signature: item.save.signature
|
|
5466
|
+
})
|
|
5467
|
+
)
|
|
5468
|
+
);
|
|
5469
|
+
} catch {
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
const batchKeys = [...batchIds];
|
|
5473
|
+
const headKeys = [...lineageIds];
|
|
5474
|
+
const signerKeys = [...signers];
|
|
5475
|
+
const contracts = [
|
|
5476
|
+
...batchKeys.map((batchId) => ({ address: batchAnchor, abi: batchAnchorAbi, functionName: "batchOf", args: [batchId] })),
|
|
5477
|
+
...headKeys.map((lineageId) => ({ address: batchAnchor, abi: batchAnchorAbi, functionName: "headCommitOf", args: [lineageId] })),
|
|
5478
|
+
...signerKeys.map(
|
|
5479
|
+
(signer) => ({
|
|
5480
|
+
address: input.deployment.capabilityRegistry,
|
|
5481
|
+
abi: capabilityRegistryAbi,
|
|
5482
|
+
functionName: "agentIdOfSigner",
|
|
5483
|
+
args: [signer]
|
|
5484
|
+
})
|
|
5485
|
+
)
|
|
5486
|
+
];
|
|
5487
|
+
const results = [];
|
|
5488
|
+
for (let offset = 0; offset < contracts.length; offset += RECORDS_PER_MULTICALL) {
|
|
5489
|
+
results.push(
|
|
5490
|
+
...await input.client.multicall({
|
|
5491
|
+
// batchSize 0 disables viem's calldata chunking: the chunk below is one aggregate3 call.
|
|
5492
|
+
batchSize: 0,
|
|
5493
|
+
multicallAddress: MULTICALL3_ADDRESS,
|
|
5494
|
+
allowFailure: false,
|
|
5495
|
+
contracts: contracts.slice(offset, offset + RECORDS_PER_MULTICALL)
|
|
5496
|
+
})
|
|
5497
|
+
);
|
|
5498
|
+
}
|
|
5499
|
+
const batches = /* @__PURE__ */ new Map();
|
|
5500
|
+
const heads = /* @__PURE__ */ new Map();
|
|
5501
|
+
const agents = /* @__PURE__ */ new Map();
|
|
5502
|
+
results.forEach((result, index) => {
|
|
5503
|
+
if (index < batchKeys.length) {
|
|
5504
|
+
batches.set(batchKeys[index], result);
|
|
5505
|
+
} else if (index < batchKeys.length + headKeys.length) {
|
|
5506
|
+
heads.set(headKeys[index - batchKeys.length], result);
|
|
5507
|
+
} else {
|
|
5508
|
+
agents.set(signerKeys[index - batchKeys.length - headKeys.length], result);
|
|
5509
|
+
}
|
|
5510
|
+
});
|
|
5511
|
+
return { batches, heads, signers: agents };
|
|
5512
|
+
}
|
|
5390
5513
|
async function checkSignedSave(input) {
|
|
5391
5514
|
const { item, chainId, batchAnchor, client } = input;
|
|
5392
5515
|
const message = wireMessage(item.save.message);
|
|
@@ -5413,7 +5536,7 @@ async function checkSignedSave(input) {
|
|
|
5413
5536
|
} catch {
|
|
5414
5537
|
return { ok: false, reason: "signature" };
|
|
5415
5538
|
}
|
|
5416
|
-
const agentId2 = await client.readContract({
|
|
5539
|
+
const agentId2 = input.lookups?.signers?.get(lowerAddress(signer)) ?? await client.readContract({
|
|
5417
5540
|
address: input.capabilityRegistry,
|
|
5418
5541
|
abi: capabilityRegistryAbi,
|
|
5419
5542
|
functionName: "agentIdOfSigner",
|
|
@@ -5435,12 +5558,12 @@ async function checkSignedSave(input) {
|
|
|
5435
5558
|
async function verifyBatchedItem(input) {
|
|
5436
5559
|
const { item, chainId, deployment, client } = input;
|
|
5437
5560
|
const batchAnchor = requireAnchor(deployment);
|
|
5438
|
-
const checked2 = await checkSignedSave({ item, chainId, batchAnchor, capabilityRegistry: deployment.capabilityRegistry, client });
|
|
5561
|
+
const checked2 = await checkSignedSave({ item, chainId, batchAnchor, capabilityRegistry: deployment.capabilityRegistry, client, lookups: input.lookups });
|
|
5439
5562
|
if (!checked2.ok) return checked2;
|
|
5440
5563
|
const { message, agentId: agentId2 } = checked2;
|
|
5441
5564
|
if (item.state !== "ANCHORED") return { ok: false, reason: "not-anchored" };
|
|
5442
5565
|
if (item.batchId === void 0) return { ok: false, reason: "unknown-batch" };
|
|
5443
|
-
const [root, blockNumber] = await client.readContract({
|
|
5566
|
+
const [root, blockNumber] = input.lookups?.batches?.get(item.batchId.toLowerCase()) ?? await client.readContract({
|
|
5444
5567
|
address: batchAnchor,
|
|
5445
5568
|
abi: batchAnchorAbi,
|
|
5446
5569
|
functionName: "batchOf",
|
|
@@ -5459,7 +5582,7 @@ async function verifyBatchedItem(input) {
|
|
|
5459
5582
|
});
|
|
5460
5583
|
if (!verifyMerkleProof(leaf, item.proof, root)) return { ok: false, reason: "proof" };
|
|
5461
5584
|
if (input.requireLatest) {
|
|
5462
|
-
const head = await client.readContract({
|
|
5585
|
+
const head = input.lookups?.heads?.get(item.lineageId.toLowerCase()) ?? await client.readContract({
|
|
5463
5586
|
address: batchAnchor,
|
|
5464
5587
|
abi: batchAnchorAbi,
|
|
5465
5588
|
functionName: "headCommitOf",
|
|
@@ -5480,7 +5603,7 @@ async function verifyPendingItem(input) {
|
|
|
5480
5603
|
const { item, chainId, deployment, client } = input;
|
|
5481
5604
|
const batchAnchor = requireAnchor(deployment);
|
|
5482
5605
|
if (item.state !== "QUEUED" && item.state !== "SUBMITTED") return { ok: false, reason: "not-pending" };
|
|
5483
|
-
const checked2 = await checkSignedSave({ item, chainId, batchAnchor, capabilityRegistry: deployment.capabilityRegistry, client });
|
|
5606
|
+
const checked2 = await checkSignedSave({ item, chainId, batchAnchor, capabilityRegistry: deployment.capabilityRegistry, client, lookups: input.lookups });
|
|
5484
5607
|
if (!checked2.ok) return checked2;
|
|
5485
5608
|
const { message, agentId: agentId2 } = checked2;
|
|
5486
5609
|
const hasAuthority = (permission) => client.readContract({
|
|
@@ -5662,8 +5785,13 @@ var MidaAgent = class {
|
|
|
5662
5785
|
const { deployment } = this.#chain;
|
|
5663
5786
|
const { objects, partial } = await this.#api.listObjects({ owner: ownerAddress, namespaceId: namespaceId2, capabilityId: capability.capabilityId });
|
|
5664
5787
|
const epochKeyFor = this.#epochKeyResolver(ownerAddress, namespaceId2, capability.capabilityId);
|
|
5788
|
+
const listed = /* @__PURE__ */ new Map();
|
|
5789
|
+
const batched = await this.#reader.getRecords(objects.map((object) => object.contextId));
|
|
5790
|
+
for (const [index, record] of batched.entries()) {
|
|
5791
|
+
listed.set(objects[index].contextId.toLowerCase(), record);
|
|
5792
|
+
}
|
|
5665
5793
|
const readObject = async (object) => {
|
|
5666
|
-
const record = await this.#verifiedRecord(ownerAddress, namespaceId2, object);
|
|
5794
|
+
const record = await this.#verifiedRecord(ownerAddress, namespaceId2, object, listed);
|
|
5667
5795
|
const epochPrivateKey = await epochKeyFor(record.readEpoch);
|
|
5668
5796
|
const payload = openContextObject({
|
|
5669
5797
|
manifest: object.manifest,
|
|
@@ -5672,7 +5800,7 @@ var MidaAgent = class {
|
|
|
5672
5800
|
epochPrivateKey,
|
|
5673
5801
|
binding: { chainId: deployment.chainId, contextRegistry: deployment.contextRegistry, contextId: record.contextId, namespaceId: namespaceId2, readEpoch: record.readEpoch }
|
|
5674
5802
|
});
|
|
5675
|
-
await this.#verifyReferences(ownerAddress, record, payload);
|
|
5803
|
+
await this.#verifyReferences(ownerAddress, record, payload, listed);
|
|
5676
5804
|
return this.#toObject(record, name, payload, { at: record.createdAt });
|
|
5677
5805
|
};
|
|
5678
5806
|
const results = new Array(objects.length);
|
|
@@ -5735,13 +5863,21 @@ var MidaAgent = class {
|
|
|
5735
5863
|
capabilityId: capability.capabilityId
|
|
5736
5864
|
});
|
|
5737
5865
|
const epochKeyFor = this.#epochKeyResolver(ownerAddress, namespaceId2, capability.capabilityId);
|
|
5866
|
+
const lookups = await prefetchBatchedLookups({
|
|
5867
|
+
items,
|
|
5868
|
+
owner: ownerAddress,
|
|
5869
|
+
namespaceId: namespaceId2,
|
|
5870
|
+
chainId: deployment.chainId,
|
|
5871
|
+
deployment,
|
|
5872
|
+
client: this.#chain.publicClient
|
|
5873
|
+
});
|
|
5738
5874
|
const blockTime = blockTimeCache(this.#chain.publicClient);
|
|
5739
5875
|
const processItem = async (item) => {
|
|
5740
5876
|
const message = item.save.message;
|
|
5741
5877
|
if (message.owner.toLowerCase() !== ownerAddress || message.namespaceId.toLowerCase() !== namespaceId2) {
|
|
5742
5878
|
return { kind: "skipped", skipped: { contextId: item.contextId, reason: "wrong-scope" } };
|
|
5743
5879
|
}
|
|
5744
|
-
const verdict = item.state === "ANCHORED" ? await verifyBatchedItem({ item, chainId: deployment.chainId, deployment, client: this.#chain.publicClient, requireLatest: true }) : await verifyPendingItem({ item, chainId: deployment.chainId, deployment, client: this.#chain.publicClient });
|
|
5880
|
+
const verdict = item.state === "ANCHORED" ? await verifyBatchedItem({ item, chainId: deployment.chainId, deployment, client: this.#chain.publicClient, requireLatest: true, lookups }) : await verifyPendingItem({ item, chainId: deployment.chainId, deployment, client: this.#chain.publicClient, lookups });
|
|
5745
5881
|
if (!verdict.ok) {
|
|
5746
5882
|
return { kind: "skipped", skipped: { contextId: item.contextId, reason: verdict.reason } };
|
|
5747
5883
|
}
|
|
@@ -6269,8 +6405,15 @@ var MidaAgent = class {
|
|
|
6269
6405
|
return pending;
|
|
6270
6406
|
};
|
|
6271
6407
|
}
|
|
6272
|
-
|
|
6273
|
-
|
|
6408
|
+
/**
|
|
6409
|
+
* The record the object claims plus every check that claim implies — the same set either way.
|
|
6410
|
+
* `listed` is the one-batch answer readWithStatus fetched: a key present in it (even a null,
|
|
6411
|
+
* the registry's "no such record") is used as-is; an id the batch somehow did not cover falls
|
|
6412
|
+
* back to a lone getRecord rather than skipping verification.
|
|
6413
|
+
*/
|
|
6414
|
+
async #verifiedRecord(owner, namespaceId2, object, listed) {
|
|
6415
|
+
const id = object.contextId.toLowerCase();
|
|
6416
|
+
const record = listed.has(id) ? listed.get(id) : await this.#reader.getRecord(object.contextId);
|
|
6274
6417
|
if (record === null || object.manifest.contextId !== object.contextId || record.owner !== owner || record.namespaceId !== namespaceId2 || record.manifestHash !== manifestHash(object.manifest) || record.ciphertextCommitment !== object.manifest.ciphertextHash) {
|
|
6275
6418
|
throw new MidaError("COMMITMENT_MISMATCH", `object ${object.contextId} does not match its Monad commitments`);
|
|
6276
6419
|
}
|
|
@@ -6282,7 +6425,7 @@ var MidaAgent = class {
|
|
|
6282
6425
|
* acknowledges an agent proposal, which is itself a CONTEXT record. USER_CONFIRMED then needs at least one
|
|
6283
6426
|
* `confirmed_from` reference, and IMPORTED/EXTERNAL_ATTESTATION at least one evidence-record target.
|
|
6284
6427
|
*/
|
|
6285
|
-
async #verifyReferences(owner, record, payload) {
|
|
6428
|
+
async #verifyReferences(owner, record, payload, listed) {
|
|
6286
6429
|
const references = payload.provenance.references ?? [];
|
|
6287
6430
|
const commitment = references.length === 0 ? zeroHash9 : evidenceCommitment(references);
|
|
6288
6431
|
if (commitment !== record.evidenceCommitment) {
|
|
@@ -6290,7 +6433,12 @@ var MidaAgent = class {
|
|
|
6290
6433
|
}
|
|
6291
6434
|
let evidenceTargets = 0;
|
|
6292
6435
|
let confirmedFrom = 0;
|
|
6293
|
-
const targets = await Promise.all(
|
|
6436
|
+
const targets = await Promise.all(
|
|
6437
|
+
references.map((reference) => {
|
|
6438
|
+
const id = reference.recordId.toLowerCase();
|
|
6439
|
+
return listed.has(id) ? listed.get(id) : this.#reader.getRecord(reference.recordId);
|
|
6440
|
+
})
|
|
6441
|
+
);
|
|
6294
6442
|
for (const [index, reference] of references.entries()) {
|
|
6295
6443
|
const target = targets[index];
|
|
6296
6444
|
if (target === null || target.owner !== owner) {
|
|
@@ -35,6 +35,22 @@ export interface ContextRecordView {
|
|
|
35
35
|
}
|
|
36
36
|
/** The most contextIds one batched `getRecords` asks about — one Multicall3 `eth_call` per batch. */
|
|
37
37
|
export declare const RECORDS_PER_MULTICALL = 200;
|
|
38
|
+
/** Registers the process-wide absent-Multicall3 reporter; `undefined` removes it. */
|
|
39
|
+
export declare function onMulticall3Absent(sink: ((chainId: bigint) => void) | undefined): void;
|
|
40
|
+
/**
|
|
41
|
+
* The process-wide Multicall3 probe for readers that are not a RegistryReader — the SDK's
|
|
42
|
+
* batched-lookup prefetch (in-39 nit 3). Resolves to this chain's record batch size
|
|
43
|
+
* (RECORDS_PER_MULTICALL where Multicall3 answers, 1 where it does not), asking the chain's
|
|
44
|
+
* `getCode` at most once per process and chain; a rejected probe is evicted and rejects here
|
|
45
|
+
* too, so the caller falls back to per-row reads and the next call asks the chain again.
|
|
46
|
+
*/
|
|
47
|
+
export declare function sharedMulticall3Probe(context: ChainContext): Promise<number>;
|
|
48
|
+
/**
|
|
49
|
+
* Forget one cached probe answer. Production chains never change whether they carry Multicall3;
|
|
50
|
+
* a test rig or a rebuilt dev chain can install the runtime mid-process (anvil_setCode), and
|
|
51
|
+
* then the next reader must ask the chain again rather than trust a stale "not here".
|
|
52
|
+
*/
|
|
53
|
+
export declare function evictMulticall3Probe(chainId: bigint): void;
|
|
38
54
|
/**
|
|
39
55
|
* Every Monad read the Context API and SDK make. Nothing here is cached: each call reads current chain state, so no
|
|
40
56
|
* local value can make Monad authorization true (§12, `currentlyAllowedByMonad`).
|
|
@@ -44,16 +60,18 @@ export declare class RegistryReader {
|
|
|
44
60
|
readonly context: ChainContext;
|
|
45
61
|
constructor(context: ChainContext);
|
|
46
62
|
/**
|
|
47
|
-
* The probed batch size once known, undefined while unprobed. Lets a BudgetedReader
|
|
48
|
-
*
|
|
63
|
+
* The probed batch size once known, undefined while unprobed. Lets a BudgetedReader answer
|
|
64
|
+
* without charging the request — but "unprobed" does not mean "no read": on a cold cache
|
|
65
|
+
* `multicall3ProbeEntry` starts the `getCode` probe right here, the chain answers it in the
|
|
66
|
+
* background, and the getter reports undefined until that answer lands (in-38 V-3).
|
|
49
67
|
*/
|
|
50
68
|
get knownRecordBatchSize(): number | undefined;
|
|
51
69
|
/**
|
|
52
70
|
* The most contextIds one `getRecords` call answers in a single chain read: RECORDS_PER_MULTICALL
|
|
53
|
-
* when the chain carries Multicall3, 1 where it does not. Probed once per
|
|
54
|
-
* and cached; a chain that cannot answer the
|
|
55
|
-
* probe
|
|
56
|
-
* not serve `eth_getCode`.
|
|
71
|
+
* when the chain carries Multicall3, 1 where it does not. Probed once per PROCESS with `getCode`
|
|
72
|
+
* — see `multicall3Probes` — and cached per reader once answered; a chain that cannot answer the
|
|
73
|
+
* probe cannot run a multicall either, so a failed probe resolves to 1 — the per-row path —
|
|
74
|
+
* rather than breaking reads on an RPC that does not serve `eth_getCode`.
|
|
57
75
|
*/
|
|
58
76
|
recordBatchSize(): Promise<number>;
|
|
59
77
|
now(): Promise<bigint>;
|
|
@@ -73,15 +91,17 @@ export declare class RegistryReader {
|
|
|
73
91
|
} | null>;
|
|
74
92
|
getRecord(contextId: Hex): Promise<ContextRecordView | null>;
|
|
75
93
|
/**
|
|
76
|
-
* `getRecord` for a batch of contextIds. With Multicall3
|
|
77
|
-
* never-anchored uploads can no longer spend a request's
|
|
78
|
-
* `ContextNotFound` revert of an orphaned upload comes back as
|
|
79
|
-
* never an error — and it is the ONLY failure that does: an
|
|
80
|
-
* any other revert throws exactly as `getRecord` throws, so
|
|
81
|
-
* silently dropped a real record. Chains without Multicall3
|
|
82
|
-
* unchanged.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
94
|
+
* `getRecord` for a batch of contextIds. With Multicall3 each `recordBatchSize()`-sized chunk of
|
|
95
|
+
* the ids is ONE `eth_call` — hundreds of never-anchored uploads can no longer spend a request's
|
|
96
|
+
* read budget a row at a time. The `ContextNotFound` revert of an orphaned upload comes back as
|
|
97
|
+
* a null entry — "not anchored", never an error — and it is the ONLY failure that does: an
|
|
98
|
+
* out-of-gas, an undecodable result or any other revert throws exactly as `getRecord` throws, so
|
|
99
|
+
* a caller can never read a list that silently dropped a real record. Chains without Multicall3
|
|
100
|
+
* fall back to one `getRecord` per id, unchanged.
|
|
101
|
+
* Chunks run in order and a failing chunk fails the whole call. Callers that pass more than
|
|
102
|
+
* `recordBatchSize()` ids pay one aggregate call per chunk — the budgeted wrapper slices to the
|
|
103
|
+
* batch size itself, so inside a request each call still costs exactly one unit of read budget
|
|
104
|
+
* (or `contextIds.length` units on the per-row path, identical to `getRecord`).
|
|
85
105
|
*/
|
|
86
106
|
getRecords(contextIds: readonly Hex[]): Promise<(ContextRecordView | null)[]>;
|
|
87
107
|
}
|
|
@@ -13,3 +13,74 @@ export declare class MidaError extends Error {
|
|
|
13
13
|
constructor(code: MidaErrorCode, detail?: string);
|
|
14
14
|
}
|
|
15
15
|
export declare function isMidaError(value: unknown, code?: MidaErrorCode): value is MidaError;
|
|
16
|
+
/**
|
|
17
|
+
* The one sentence the owner page shows when a request carries a refused character in a field it renders — a manifest name, a folder root, a project label.
|
|
18
|
+
* One fixed sentence, with no field name or code, so the refusal itself can never smuggle part
|
|
19
|
+
* of the request onto the page (in-27 R-1). This string is final copy.
|
|
20
|
+
*/
|
|
21
|
+
export declare const UNACCEPTABLE_REQUEST_TEXT = "This request contains characters Mida does not accept, so this page will not show or sign it.";
|
|
22
|
+
/**
|
|
23
|
+
* The characters that must never reach a field a page or a terminal renders. The set is wider
|
|
24
|
+
* than C0+DEL (in-30 T-3): every control character, the Unicode line and paragraph separators,
|
|
25
|
+
* the zero-width space (200B), the directional marks (200E-200F), the bidirectional controls
|
|
26
|
+
* (202A-202E, 2066-2069) and the BOM. Each can forge a rendered line or hide inside one.
|
|
27
|
+
* The joiners are NOT refused (in-31 V-3): the zero-width non-joiner (200C) is part of Persian
|
|
28
|
+
* and Indic spelling and the zero-width joiner (200D) holds emoji sequences together — neither
|
|
29
|
+
* can mint or hide a line.
|
|
30
|
+
*/
|
|
31
|
+
export declare const UNACCEPTABLE_REQUEST_CHARS: RegExp;
|
|
32
|
+
/**
|
|
33
|
+
* A refused character folded to a single space — for a display-only field that is size-checked
|
|
34
|
+
* but not refused (a manifest's purpose description or scope reason, in-30 T-3). Validation keeps
|
|
35
|
+
* the bytes untouched so the manifest's hash still matches what was registered; whatever later
|
|
36
|
+
* renders the field passes it through here first.
|
|
37
|
+
*/
|
|
38
|
+
export declare function displaySafeText(value: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* The multi-line form of displaySafeText (in-40 L-4) — for text whose own line breaks must
|
|
41
|
+
* survive, like a handoff shown by `mida task show`. The Unicode line and paragraph separators
|
|
42
|
+
* become `\n` rather than spaces so a line break can never hide, and `\n` and `\t` stay; every
|
|
43
|
+
* other refused character still folds to one space. No collapsing, no trimming: the writer's
|
|
44
|
+
* line structure is the contract.
|
|
45
|
+
*/
|
|
46
|
+
export declare function displaySafeBlock(value: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* The single-line form of displaySafeText (in-41 U-2) — for text Mida did not write that must
|
|
49
|
+
* print as ONE line: a checkpoint's stored task name, a reason the store returned, a code an
|
|
50
|
+
* RPC error carried. Every refused character folds to a space, whitespace runs collapse to one
|
|
51
|
+
* space, the ends are trimmed, and a line still longer than `maxChars` ends in `…` inside the
|
|
52
|
+
* cap — no value from a store or a chain can open a control sequence or forge a second line.
|
|
53
|
+
*/
|
|
54
|
+
export declare function displaySafeLine(value: string, maxChars: number): string;
|
|
55
|
+
/**
|
|
56
|
+
* Five or more combining marks in a row is a Zalgo stack, not a name: a browser piles them
|
|
57
|
+
* vertically without a limit and lets the ink paint over the lines above — the advisor
|
|
58
|
+
* verdict and warnings on the same page (in-37, review 3.1). Four stays legal — Tibetan
|
|
59
|
+
* stacks three, and Devanagari, Hebrew, Arabic and Thai all stay under. The owner-link
|
|
60
|
+
* parser applies the same run limit to every rendered field, so a stack cannot be carried
|
|
61
|
+
* in through a label or a root either.
|
|
62
|
+
*/
|
|
63
|
+
export declare const STACKED_MARK_RUN: RegExp;
|
|
64
|
+
/**
|
|
65
|
+
* The one agent-name rule, shared by the manifest validator, the owner-link parser and /me
|
|
66
|
+
* (in-32 X-2, in-34; tightened in-37) — an allow-list, not a deny-list. A name renders
|
|
67
|
+
* inside the summary's `Agent "…"` quotes, so it admits only the characters a name needs
|
|
68
|
+
* and refuses everything else: a quote shape could close or imitate the quotes, a colon or
|
|
69
|
+
* bracket could forge a rendered line, a symbol or emoji needs no reason to be in a name at
|
|
70
|
+
* all. in-37 adds the three refusals a class allow-list cannot express: the first character
|
|
71
|
+
* is a letter or number — never a combining mark, which would strike the quote itself —
|
|
72
|
+
* the eight letter-class look-alikes refuse by name, and five or more combining marks in a
|
|
73
|
+
* row refuse as a Zalgo stack. A name that is empty after trimming, or one that opens with
|
|
74
|
+
* a space or a joiner, is refused too — it would render as `Agent ""` or smuggle a space
|
|
75
|
+
* into the first character. The byte-length limit stays where it always lived — in the
|
|
76
|
+
* manifest validator — not here.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isAcceptableAgentName(name: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* INVALID_WIRE whose message is already the owner-facing sentence. MidaError prefixes its detail
|
|
81
|
+
* with the code — "INVALID_WIRE: This request…" is not the page's text — so this subclass keeps
|
|
82
|
+
* the code for API/log handling and the bare sentence for describeError.
|
|
83
|
+
*/
|
|
84
|
+
export declare class UnacceptableCharactersError extends MidaError {
|
|
85
|
+
constructor();
|
|
86
|
+
}
|
|
@@ -38,6 +38,38 @@ export declare function signBatchSave(input: {
|
|
|
38
38
|
batchAnchor: Address;
|
|
39
39
|
message: BatchSaveMessage;
|
|
40
40
|
}): Promise<Hex>;
|
|
41
|
+
/**
|
|
42
|
+
* The chain answers a batched read needs per distinct VALUE rather than per row (in-38 V-4):
|
|
43
|
+
* every batch's `batchOf` ([root, anchor block]), every lineage's `headCommitOf`, and every
|
|
44
|
+
* signer's `agentIdOfSigner`, fetched through Multicall3 before the row checks run. The maps live
|
|
45
|
+
* for ONE `readBatchedWithStatus` call and nothing may outlive it — a lineage head or an
|
|
46
|
+
* authority can move before the next read.
|
|
47
|
+
*/
|
|
48
|
+
export interface BatchedReadLookups {
|
|
49
|
+
/** batchId (lowercased) → the `batchOf` answer: [root, blockNumber]. */
|
|
50
|
+
readonly batches?: ReadonlyMap<Hex, readonly [Hex, bigint]>;
|
|
51
|
+
/** lineageId (lowercased) → the `headCommitOf` answer. */
|
|
52
|
+
readonly heads?: ReadonlyMap<Hex, Hex>;
|
|
53
|
+
/** signer (lowercased) → the `agentIdOfSigner` answer. */
|
|
54
|
+
readonly signers?: ReadonlyMap<Address, Hex>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fetches the three distinct-value sets one batched read will consult, in aggregate calls of at
|
|
58
|
+
* most RECORDS_PER_MULTICALL contract reads with `allowFailure: false` — a failed lookup fails
|
|
59
|
+
* the read exactly as a failed `readContract` does today. Returns undefined where the chain
|
|
60
|
+
* carries no Multicall3 (no code at the canonical address, or the getCode probe itself fails —
|
|
61
|
+
* the probe is the process-wide one shared with every RegistryReader, in-38 V-3): the verifiers
|
|
62
|
+
* then read per row, unchanged. Only rows that would reach a chain check spend a
|
|
63
|
+
* lookup — wrong-scope rows and states that refuse before the signature check contribute nothing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function prefetchBatchedLookups(input: {
|
|
66
|
+
items: readonly BatchedReadItem[];
|
|
67
|
+
owner: Address;
|
|
68
|
+
namespaceId: Hex;
|
|
69
|
+
chainId: bigint;
|
|
70
|
+
deployment: Deployment;
|
|
71
|
+
client: PublicClient;
|
|
72
|
+
}): Promise<BatchedReadLookups | undefined>;
|
|
41
73
|
/**
|
|
42
74
|
* The five-check anchored read. Chain state is the only authority: the signer→agent lookup, the
|
|
43
75
|
* batch root, and the lineage head all come from the contracts through `client`; the store supplies
|
|
@@ -50,6 +82,7 @@ export declare function verifyBatchedItem(input: {
|
|
|
50
82
|
deployment: Deployment;
|
|
51
83
|
client: PublicClient;
|
|
52
84
|
requireLatest: boolean;
|
|
85
|
+
lookups?: BatchedReadLookups;
|
|
53
86
|
}): Promise<BatchedVerdict>;
|
|
54
87
|
/**
|
|
55
88
|
* Amendment B.2 pending read: everything checkable without anchor inclusion — bytes, signature,
|
|
@@ -65,4 +98,5 @@ export declare function verifyPendingItem(input: {
|
|
|
65
98
|
chainId: bigint;
|
|
66
99
|
deployment: Deployment;
|
|
67
100
|
client: PublicClient;
|
|
101
|
+
lookups?: BatchedReadLookups;
|
|
68
102
|
}): Promise<PendingVerdict>;
|