@jterrazz/attestation 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/bin/attestation +2 -0
- package/dist/audit.cjs +57 -0
- package/dist/audit.cjs.map +1 -0
- package/dist/audit.js +52 -0
- package/dist/audit.js.map +1 -0
- package/dist/browser.cjs +183 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +111 -0
- package/dist/browser.d.ts +111 -0
- package/dist/browser.js +168 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.cjs +284 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +285 -0
- package/dist/cli.js.map +1 -0
- package/dist/eip712-schema.cjs +96 -0
- package/dist/eip712-schema.cjs.map +1 -0
- package/dist/eip712-schema.d.cts +75 -0
- package/dist/eip712-schema.d.ts +75 -0
- package/dist/eip712-schema.js +73 -0
- package/dist/eip712-schema.js.map +1 -0
- package/dist/index.cjs +21 -0
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +4 -0
- package/dist/node.cjs +279 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +87 -0
- package/dist/node.d.ts +87 -0
- package/dist/node.js +274 -0
- package/dist/node.js.map +1 -0
- package/dist/sign-flow.cjs +323 -0
- package/dist/sign-flow.cjs.map +1 -0
- package/dist/sign-flow.js +277 -0
- package/dist/sign-flow.js.map +1 -0
- package/dist/verify.cjs +276 -0
- package/dist/verify.cjs.map +1 -0
- package/dist/verify.d.cts +105 -0
- package/dist/verify.d.ts +105 -0
- package/dist/verify.js +199 -0
- package/dist/verify.js.map +1 -0
- package/package.json +67 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_verify = require("./verify.cjs");
|
|
3
|
+
const require_audit = require("./audit.cjs");
|
|
4
|
+
const require_eip712_schema = require("./eip712-schema.cjs");
|
|
5
|
+
exports.ATTESTATION_DOMAIN_V1 = require_eip712_schema.ATTESTATION_DOMAIN_V1;
|
|
6
|
+
exports.ATTESTATION_PRIMARY_TYPE = require_eip712_schema.ATTESTATION_PRIMARY_TYPE;
|
|
7
|
+
exports.ATTESTATION_TYPES_V1 = require_eip712_schema.ATTESTATION_TYPES_V1;
|
|
8
|
+
exports.CANONICAL_VERSION = require_verify.CANONICAL_VERSION;
|
|
9
|
+
exports.InvalidContentError = require_verify.InvalidContentError;
|
|
10
|
+
exports.NO_PRIOR_ATTESTATION = require_eip712_schema.NO_PRIOR_ATTESTATION;
|
|
11
|
+
exports.SCHEMA_VERSION = require_verify.SCHEMA_VERSION;
|
|
12
|
+
exports.audit = require_audit.audit;
|
|
13
|
+
exports.buildAttestationMessage = require_verify.buildAttestationMessage;
|
|
14
|
+
exports.canonicalize = require_verify.canonicalize;
|
|
15
|
+
exports.createAttestation = require_verify.createAttestation;
|
|
16
|
+
exports.fromStored = require_verify.fromStored;
|
|
17
|
+
exports.parse = require_verify.parse;
|
|
18
|
+
exports.signAttestation = require_verify.signAttestation;
|
|
19
|
+
exports.stringify = require_verify.stringify;
|
|
20
|
+
exports.toStored = require_verify.toStored;
|
|
21
|
+
exports.verifyAttestation = require_verify.verifyAttestation;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { _ as InvalidContentError, a as stringify, c as buildAttestationMessage, d as SignedAttestation, f as StoredAttestation, g as VerifyResult, h as VerifyOk, i as parse, l as createAttestation, m as VerifyFail, n as verifyAttestation, o as toStored, p as VerifyError, r as fromStored, s as CreateAttestationInput, t as VerifyInput, u as signAttestation, v as canonicalize } from "./verify.cjs";
|
|
2
|
+
import { a as ArticleSubject, i as ArticleClaims, n as ATTESTATION_PRIMARY_TYPE, o as AttestationMessage, r as ATTESTATION_TYPES_V1, s as NO_PRIOR_ATTESTATION, t as ATTESTATION_DOMAIN_V1 } from "./eip712-schema.cjs";
|
|
3
|
+
//#region src/version.d.ts
|
|
4
|
+
declare const SCHEMA_VERSION: 1;
|
|
5
|
+
declare const CANONICAL_VERSION: 1;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/core/audit.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Suspicious-character audit — runs at SIGN time, never at verify time.
|
|
10
|
+
*
|
|
11
|
+
* Verify must always remain a function of canonicalize() alone, never adding
|
|
12
|
+
* stricter rules. Audit is a separate, mutable layer of advice for the author:
|
|
13
|
+
* "you probably don't want these invisible chars in a published article".
|
|
14
|
+
*/
|
|
15
|
+
type AuditFinding = {
|
|
16
|
+
line: number;
|
|
17
|
+
column: number;
|
|
18
|
+
codepoint: number;
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
declare function audit(canonical: Uint8Array): AuditFinding[];
|
|
22
|
+
//#endregion
|
|
23
|
+
export { ATTESTATION_DOMAIN_V1, ATTESTATION_PRIMARY_TYPE, ATTESTATION_TYPES_V1, type ArticleClaims, type ArticleSubject, type AttestationMessage, type AuditFinding, CANONICAL_VERSION, type CreateAttestationInput, InvalidContentError, NO_PRIOR_ATTESTATION, SCHEMA_VERSION, type SignedAttestation, type StoredAttestation, type VerifyError, type VerifyFail, type VerifyInput, type VerifyOk, type VerifyResult, audit, buildAttestationMessage, canonicalize, createAttestation, fromStored, parse, signAttestation, stringify, toStored, verifyAttestation };
|
|
24
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { _ as InvalidContentError, a as stringify, c as buildAttestationMessage, d as SignedAttestation, f as StoredAttestation, g as VerifyResult, h as VerifyOk, i as parse, l as createAttestation, m as VerifyFail, n as verifyAttestation, o as toStored, p as VerifyError, r as fromStored, s as CreateAttestationInput, t as VerifyInput, u as signAttestation, v as canonicalize } from "./verify.js";
|
|
2
|
+
import { a as ArticleSubject, i as ArticleClaims, n as ATTESTATION_PRIMARY_TYPE, o as AttestationMessage, r as ATTESTATION_TYPES_V1, s as NO_PRIOR_ATTESTATION, t as ATTESTATION_DOMAIN_V1 } from "./eip712-schema.js";
|
|
3
|
+
//#region src/version.d.ts
|
|
4
|
+
declare const SCHEMA_VERSION: 1;
|
|
5
|
+
declare const CANONICAL_VERSION: 1;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/core/audit.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Suspicious-character audit — runs at SIGN time, never at verify time.
|
|
10
|
+
*
|
|
11
|
+
* Verify must always remain a function of canonicalize() alone, never adding
|
|
12
|
+
* stricter rules. Audit is a separate, mutable layer of advice for the author:
|
|
13
|
+
* "you probably don't want these invisible chars in a published article".
|
|
14
|
+
*/
|
|
15
|
+
type AuditFinding = {
|
|
16
|
+
line: number;
|
|
17
|
+
column: number;
|
|
18
|
+
codepoint: number;
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
declare function audit(canonical: Uint8Array): AuditFinding[];
|
|
22
|
+
//#endregion
|
|
23
|
+
export { ATTESTATION_DOMAIN_V1, ATTESTATION_PRIMARY_TYPE, ATTESTATION_TYPES_V1, type ArticleClaims, type ArticleSubject, type AttestationMessage, type AuditFinding, CANONICAL_VERSION, type CreateAttestationInput, InvalidContentError, NO_PRIOR_ATTESTATION, SCHEMA_VERSION, type SignedAttestation, type StoredAttestation, type VerifyError, type VerifyFail, type VerifyInput, type VerifyOk, type VerifyResult, audit, buildAttestationMessage, canonicalize, createAttestation, fromStored, parse, signAttestation, stringify, toStored, verifyAttestation };
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { a as toStored, c as signAttestation, d as canonicalize, f as CANONICAL_VERSION, i as stringify, n as fromStored, o as buildAttestationMessage, p as SCHEMA_VERSION, r as parse, s as createAttestation, t as verifyAttestation, u as InvalidContentError } from "./verify.js";
|
|
2
|
+
import { t as audit } from "./audit.js";
|
|
3
|
+
import { i as NO_PRIOR_ATTESTATION, n as ATTESTATION_PRIMARY_TYPE, r as ATTESTATION_TYPES_V1, t as ATTESTATION_DOMAIN_V1 } from "./eip712-schema.js";
|
|
4
|
+
export { ATTESTATION_DOMAIN_V1, ATTESTATION_PRIMARY_TYPE, ATTESTATION_TYPES_V1, CANONICAL_VERSION, InvalidContentError, NO_PRIOR_ATTESTATION, SCHEMA_VERSION, audit, buildAttestationMessage, canonicalize, createAttestation, fromStored, parse, signAttestation, stringify, toStored, verifyAttestation };
|
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_sign_flow = require("./sign-flow.cjs");
|
|
3
|
+
const require_eip712_schema = require("./eip712-schema.cjs");
|
|
4
|
+
let node_http = require("node:http");
|
|
5
|
+
//#region src/eth/sign-batch-page-html.ts
|
|
6
|
+
/**
|
|
7
|
+
* HTML page served by the local CLI server during batch signing. Loads the
|
|
8
|
+
* full list of EIP-712 typed-data messages, walks them sequentially, and POSTs
|
|
9
|
+
* each signature back as MetaMask returns it. The server closes after the user
|
|
10
|
+
* either signs the last entry or clicks "Done".
|
|
11
|
+
*/
|
|
12
|
+
function buildBatchSignPageHtml(entries) {
|
|
13
|
+
return `<!doctype html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8" />
|
|
17
|
+
<title>Sign all attestations</title>
|
|
18
|
+
<style>
|
|
19
|
+
* { box-sizing: border-box; }
|
|
20
|
+
body { font-family: system-ui, sans-serif; max-width: 720px; margin: 3rem auto; padding: 0 1rem; line-height: 1.5; color: #111; }
|
|
21
|
+
h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
|
|
22
|
+
.lead { color: #666; margin: 0 0 2rem; }
|
|
23
|
+
.controls { display: flex; gap: 0.75rem; margin-bottom: 1.5rem; align-items: center; }
|
|
24
|
+
button { padding: 0.6rem 1rem; font-size: 0.95rem; cursor: pointer; border: 1px solid #ccc; background: #fff; border-radius: 6px; }
|
|
25
|
+
button.primary { background: #111; color: #fff; border-color: #111; }
|
|
26
|
+
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
27
|
+
.progress { font-size: 0.85rem; color: #666; }
|
|
28
|
+
ul.entries { list-style: none; padding: 0; margin: 0; border-top: 1px solid #eee; }
|
|
29
|
+
ul.entries li { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid #eee; font-size: 0.9rem; }
|
|
30
|
+
.pill { font-family: ui-monospace, monospace; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; padding: 2px 8px; border-radius: 999px; border: 1px solid; }
|
|
31
|
+
.pill.idle { color: #999; border-color: #ddd; }
|
|
32
|
+
.pill.active { color: #0070f3; border-color: #c6dfff; background: #f0f7ff; }
|
|
33
|
+
.pill.signed { color: #0a7d22; border-color: #b8e6c1; background: #f0faf3; }
|
|
34
|
+
.pill.skipped { color: #b78403; border-color: #f1d57f; background: #fffaeb; }
|
|
35
|
+
.pill.error { color: #b00020; border-color: #f5b8c1; background: #fef0f2; }
|
|
36
|
+
.label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
37
|
+
.err-msg { font-size: 0.75rem; color: #b00020; padding: 0 0 0.5rem 0; }
|
|
38
|
+
pre.preview { max-height: 200px; overflow: auto; font-size: 0.8rem; background: #f7f7f7; padding: 0.75rem; border-radius: 6px; margin: 0 0 1.5rem; }
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<h1>Sign attestations</h1>
|
|
43
|
+
<p class="lead" id="lead"></p>
|
|
44
|
+
<div class="controls">
|
|
45
|
+
<button class="primary" id="run">Sign all</button>
|
|
46
|
+
<button id="finish" disabled>Done</button>
|
|
47
|
+
<span class="progress" id="progress"></span>
|
|
48
|
+
</div>
|
|
49
|
+
<ul class="entries" id="entries"></ul>
|
|
50
|
+
<script>
|
|
51
|
+
const ENTRIES = JSON.parse(\`${JSON.stringify(entries).replace(/\\/g, String.raw`\\`).replace(/`/g, "\\`").replace(/<\/script>/gi, String.raw`<\/script>`)}\`);
|
|
52
|
+
const ul = document.getElementById('entries');
|
|
53
|
+
const lead = document.getElementById('lead');
|
|
54
|
+
const progress = document.getElementById('progress');
|
|
55
|
+
const runBtn = document.getElementById('run');
|
|
56
|
+
const finishBtn = document.getElementById('finish');
|
|
57
|
+
|
|
58
|
+
lead.textContent = ENTRIES.length + ' article' + (ENTRIES.length === 1 ? '' : 's') + ' to sign. MetaMask will pop up once per article — review the typed data and confirm.';
|
|
59
|
+
progress.textContent = '0 / ' + ENTRIES.length;
|
|
60
|
+
|
|
61
|
+
const rows = ENTRIES.map((e, i) => {
|
|
62
|
+
const li = document.createElement('li');
|
|
63
|
+
const label = document.createElement('span');
|
|
64
|
+
label.className = 'label';
|
|
65
|
+
label.textContent = (i + 1) + '. ' + e.label;
|
|
66
|
+
const pill = document.createElement('span');
|
|
67
|
+
pill.className = 'pill idle';
|
|
68
|
+
pill.textContent = 'idle';
|
|
69
|
+
li.appendChild(pill);
|
|
70
|
+
li.appendChild(label);
|
|
71
|
+
ul.appendChild(li);
|
|
72
|
+
return { pill, li };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
let signed = 0;
|
|
76
|
+
|
|
77
|
+
async function signOne(i) {
|
|
78
|
+
const entry = ENTRIES[i];
|
|
79
|
+
rows[i].pill.className = 'pill active';
|
|
80
|
+
rows[i].pill.textContent = 'signing';
|
|
81
|
+
|
|
82
|
+
if (!window.ethereum) throw new Error('No window.ethereum provider');
|
|
83
|
+
|
|
84
|
+
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
85
|
+
const signer = accounts[0];
|
|
86
|
+
|
|
87
|
+
let signature;
|
|
88
|
+
try {
|
|
89
|
+
signature = await window.ethereum.request({
|
|
90
|
+
method: 'eth_signTypedData_v4',
|
|
91
|
+
params: [signer, JSON.stringify(entry.typedData)],
|
|
92
|
+
});
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const msg = err && err.message ? err.message : String(err);
|
|
95
|
+
rows[i].pill.className = 'pill error';
|
|
96
|
+
rows[i].pill.textContent = 'rejected';
|
|
97
|
+
const errLi = document.createElement('div');
|
|
98
|
+
errLi.className = 'err-msg';
|
|
99
|
+
errLi.textContent = msg;
|
|
100
|
+
rows[i].li.appendChild(errLi);
|
|
101
|
+
await fetch('/skip', {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: { 'content-type': 'application/json' },
|
|
104
|
+
body: JSON.stringify({ id: entry.id, reason: msg }),
|
|
105
|
+
});
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const res = await fetch('/sig', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: { 'content-type': 'application/json' },
|
|
112
|
+
body: JSON.stringify({ id: entry.id, signature, signerAddress: signer }),
|
|
113
|
+
});
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
rows[i].pill.className = 'pill error';
|
|
116
|
+
rows[i].pill.textContent = 'server-rejected';
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
rows[i].pill.className = 'pill signed';
|
|
120
|
+
rows[i].pill.textContent = 'signed';
|
|
121
|
+
signed++;
|
|
122
|
+
progress.textContent = signed + ' / ' + ENTRIES.length;
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
runBtn.addEventListener('click', async () => {
|
|
127
|
+
runBtn.disabled = true;
|
|
128
|
+
for (let i = 0; i < ENTRIES.length; i++) {
|
|
129
|
+
try { await signOne(i); } catch (err) {
|
|
130
|
+
console.error(err);
|
|
131
|
+
rows[i].pill.className = 'pill error';
|
|
132
|
+
rows[i].pill.textContent = 'error';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
finishBtn.disabled = false;
|
|
136
|
+
finishBtn.classList.add('primary');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
finishBtn.addEventListener('click', async () => {
|
|
140
|
+
finishBtn.disabled = true;
|
|
141
|
+
await fetch('/done', { method: 'POST' });
|
|
142
|
+
document.body.innerHTML = '<h1>Done.</h1><p>You can close this tab.</p>';
|
|
143
|
+
});
|
|
144
|
+
<\/script>
|
|
145
|
+
</body>
|
|
146
|
+
</html>`;
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/eth/sign-batch-flow.ts
|
|
150
|
+
const DEFAULT_TIMEOUT_MS = 1800 * 1e3;
|
|
151
|
+
async function signBatchViaBrowser(opts) {
|
|
152
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
153
|
+
const html = buildBatchSignPageHtml(opts.entries.map((e) => ({
|
|
154
|
+
id: e.id,
|
|
155
|
+
label: e.label,
|
|
156
|
+
typedData: {
|
|
157
|
+
domain: require_eip712_schema.ATTESTATION_DOMAIN_V1,
|
|
158
|
+
message: serializeMessageForBrowser(e.message),
|
|
159
|
+
primaryType: require_eip712_schema.ATTESTATION_PRIMARY_TYPE,
|
|
160
|
+
types: {
|
|
161
|
+
EIP712Domain: domainTypes(),
|
|
162
|
+
...require_eip712_schema.ATTESTATION_TYPES_V1
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})));
|
|
166
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
167
|
+
const skipped = /* @__PURE__ */ new Map();
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
const server = (0, node_http.createServer)((req, res) => {
|
|
170
|
+
if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) {
|
|
171
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
172
|
+
res.end(html);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (req.method === "POST" && (req.url === "/sig" || req.url === "/skip")) {
|
|
176
|
+
let body = "";
|
|
177
|
+
req.on("data", (chunk) => {
|
|
178
|
+
body += chunk.toString("utf8");
|
|
179
|
+
});
|
|
180
|
+
req.on("end", () => {
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(body);
|
|
184
|
+
} catch {
|
|
185
|
+
res.writeHead(400);
|
|
186
|
+
res.end("Parse error");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (req.url === "/sig") {
|
|
190
|
+
if (typeof parsed.signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(parsed.signature) || typeof parsed.signerAddress !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(parsed.signerAddress)) {
|
|
191
|
+
res.writeHead(400);
|
|
192
|
+
res.end("Invalid payload");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
signatures.set(parsed.id, {
|
|
196
|
+
signature: parsed.signature,
|
|
197
|
+
signerAddress: parsed.signerAddress
|
|
198
|
+
});
|
|
199
|
+
} else skipped.set(parsed.id, parsed.reason ?? "skipped");
|
|
200
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
201
|
+
res.end("OK");
|
|
202
|
+
});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (req.method === "POST" && req.url === "/done") {
|
|
206
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
207
|
+
res.end("OK");
|
|
208
|
+
res.on("finish", () => {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
server.close();
|
|
211
|
+
resolve({
|
|
212
|
+
signatures,
|
|
213
|
+
skipped
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
res.writeHead(404);
|
|
219
|
+
res.end("Not found");
|
|
220
|
+
});
|
|
221
|
+
const timer = setTimeout(() => {
|
|
222
|
+
server.close();
|
|
223
|
+
reject(/* @__PURE__ */ new Error(`Batch sign flow timed out after ${timeoutMs}ms`));
|
|
224
|
+
}, timeoutMs);
|
|
225
|
+
server.on("error", (err) => {
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
reject(err);
|
|
228
|
+
});
|
|
229
|
+
server.listen(0, "127.0.0.1", () => {
|
|
230
|
+
const url = `http://127.0.0.1:${server.address().port}/`;
|
|
231
|
+
const ready = opts.onUrlReady ?? defaultOpenInBrowser;
|
|
232
|
+
Promise.resolve(ready(url)).catch((error) => {
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
server.close();
|
|
235
|
+
reject(error);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function defaultOpenInBrowser(url) {
|
|
241
|
+
const { default: open } = await import("open");
|
|
242
|
+
await open(url);
|
|
243
|
+
}
|
|
244
|
+
function serializeMessageForBrowser(message) {
|
|
245
|
+
return {
|
|
246
|
+
claims: {
|
|
247
|
+
priorAttestation: message.claims.priorAttestation,
|
|
248
|
+
publishedAt: message.claims.publishedAt.toString(),
|
|
249
|
+
revision: message.claims.revision,
|
|
250
|
+
slug: message.claims.slug
|
|
251
|
+
},
|
|
252
|
+
schemaVersion: message.schemaVersion,
|
|
253
|
+
subject: message.subject
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function domainTypes() {
|
|
257
|
+
return [
|
|
258
|
+
{
|
|
259
|
+
name: "name",
|
|
260
|
+
type: "string"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "version",
|
|
264
|
+
type: "string"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "chainId",
|
|
268
|
+
type: "uint256"
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
//#endregion
|
|
273
|
+
exports.signBatchViaBrowser = signBatchViaBrowser;
|
|
274
|
+
exports.signViaBrowser = require_sign_flow.signViaBrowser;
|
|
275
|
+
exports.stampDigest = require_sign_flow.stampDigest;
|
|
276
|
+
exports.upgradeProof = require_sign_flow.upgradeProof;
|
|
277
|
+
exports.verifyOts = require_sign_flow.verifyOts;
|
|
278
|
+
|
|
279
|
+
//# sourceMappingURL=node.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.cjs","names":["ATTESTATION_DOMAIN_V1","ATTESTATION_PRIMARY_TYPE","ATTESTATION_TYPES_V1"],"sources":["../src/eth/sign-batch-page-html.ts","../src/eth/sign-batch-flow.ts"],"sourcesContent":["/**\n * HTML page served by the local CLI server during batch signing. Loads the\n * full list of EIP-712 typed-data messages, walks them sequentially, and POSTs\n * each signature back as MetaMask returns it. The server closes after the user\n * either signs the last entry or clicks \"Done\".\n */\nexport function buildBatchSignPageHtml(\n entries: Array<{ id: string; label: string; typedData: object }>,\n): string {\n const escaped = JSON.stringify(entries)\n .replace(/\\\\/g, String.raw`\\\\`)\n .replace(/`/g, '\\\\`')\n .replace(/<\\/script>/gi, String.raw`<\\/script>`);\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>Sign all attestations</title>\n<style>\n * { box-sizing: border-box; }\n body { font-family: system-ui, sans-serif; max-width: 720px; margin: 3rem auto; padding: 0 1rem; line-height: 1.5; color: #111; }\n h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }\n .lead { color: #666; margin: 0 0 2rem; }\n .controls { display: flex; gap: 0.75rem; margin-bottom: 1.5rem; align-items: center; }\n button { padding: 0.6rem 1rem; font-size: 0.95rem; cursor: pointer; border: 1px solid #ccc; background: #fff; border-radius: 6px; }\n button.primary { background: #111; color: #fff; border-color: #111; }\n button:disabled { opacity: 0.5; cursor: not-allowed; }\n .progress { font-size: 0.85rem; color: #666; }\n ul.entries { list-style: none; padding: 0; margin: 0; border-top: 1px solid #eee; }\n ul.entries li { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid #eee; font-size: 0.9rem; }\n .pill { font-family: ui-monospace, monospace; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; padding: 2px 8px; border-radius: 999px; border: 1px solid; }\n .pill.idle { color: #999; border-color: #ddd; }\n .pill.active { color: #0070f3; border-color: #c6dfff; background: #f0f7ff; }\n .pill.signed { color: #0a7d22; border-color: #b8e6c1; background: #f0faf3; }\n .pill.skipped { color: #b78403; border-color: #f1d57f; background: #fffaeb; }\n .pill.error { color: #b00020; border-color: #f5b8c1; background: #fef0f2; }\n .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .err-msg { font-size: 0.75rem; color: #b00020; padding: 0 0 0.5rem 0; }\n pre.preview { max-height: 200px; overflow: auto; font-size: 0.8rem; background: #f7f7f7; padding: 0.75rem; border-radius: 6px; margin: 0 0 1.5rem; }\n</style>\n</head>\n<body>\n<h1>Sign attestations</h1>\n<p class=\"lead\" id=\"lead\"></p>\n<div class=\"controls\">\n <button class=\"primary\" id=\"run\">Sign all</button>\n <button id=\"finish\" disabled>Done</button>\n <span class=\"progress\" id=\"progress\"></span>\n</div>\n<ul class=\"entries\" id=\"entries\"></ul>\n<script>\n const ENTRIES = JSON.parse(\\`${escaped}\\`);\n const ul = document.getElementById('entries');\n const lead = document.getElementById('lead');\n const progress = document.getElementById('progress');\n const runBtn = document.getElementById('run');\n const finishBtn = document.getElementById('finish');\n\n lead.textContent = ENTRIES.length + ' article' + (ENTRIES.length === 1 ? '' : 's') + ' to sign. MetaMask will pop up once per article — review the typed data and confirm.';\n progress.textContent = '0 / ' + ENTRIES.length;\n\n const rows = ENTRIES.map((e, i) => {\n const li = document.createElement('li');\n const label = document.createElement('span');\n label.className = 'label';\n label.textContent = (i + 1) + '. ' + e.label;\n const pill = document.createElement('span');\n pill.className = 'pill idle';\n pill.textContent = 'idle';\n li.appendChild(pill);\n li.appendChild(label);\n ul.appendChild(li);\n return { pill, li };\n });\n\n let signed = 0;\n\n async function signOne(i) {\n const entry = ENTRIES[i];\n rows[i].pill.className = 'pill active';\n rows[i].pill.textContent = 'signing';\n\n if (!window.ethereum) throw new Error('No window.ethereum provider');\n\n const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });\n const signer = accounts[0];\n\n let signature;\n try {\n signature = await window.ethereum.request({\n method: 'eth_signTypedData_v4',\n params: [signer, JSON.stringify(entry.typedData)],\n });\n } catch (err) {\n const msg = err && err.message ? err.message : String(err);\n rows[i].pill.className = 'pill error';\n rows[i].pill.textContent = 'rejected';\n const errLi = document.createElement('div');\n errLi.className = 'err-msg';\n errLi.textContent = msg;\n rows[i].li.appendChild(errLi);\n await fetch('/skip', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: entry.id, reason: msg }),\n });\n return false;\n }\n\n const res = await fetch('/sig', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: entry.id, signature, signerAddress: signer }),\n });\n if (!res.ok) {\n rows[i].pill.className = 'pill error';\n rows[i].pill.textContent = 'server-rejected';\n return false;\n }\n rows[i].pill.className = 'pill signed';\n rows[i].pill.textContent = 'signed';\n signed++;\n progress.textContent = signed + ' / ' + ENTRIES.length;\n return true;\n }\n\n runBtn.addEventListener('click', async () => {\n runBtn.disabled = true;\n for (let i = 0; i < ENTRIES.length; i++) {\n try { await signOne(i); } catch (err) {\n console.error(err);\n rows[i].pill.className = 'pill error';\n rows[i].pill.textContent = 'error';\n }\n }\n finishBtn.disabled = false;\n finishBtn.classList.add('primary');\n });\n\n finishBtn.addEventListener('click', async () => {\n finishBtn.disabled = true;\n await fetch('/done', { method: 'POST' });\n document.body.innerHTML = '<h1>Done.</h1><p>You can close this tab.</p>';\n });\n</script>\n</body>\n</html>`;\n}\n","import { createServer } from 'node:http';\nimport { type AddressInfo } from 'node:net';\n\nimport {\n ATTESTATION_DOMAIN_V1,\n ATTESTATION_PRIMARY_TYPE,\n ATTESTATION_TYPES_V1,\n type AttestationMessage,\n} from '../core/eip712-schema.js';\nimport { buildBatchSignPageHtml } from './sign-batch-page-html.js';\n\nexport type BatchSignEntry = {\n /** Unique key — used to correlate signatures back to entries. */\n id: string;\n /** Human-readable label shown in the browser UI. */\n label: string;\n /** EIP-712 message to sign. */\n message: AttestationMessage;\n};\n\nexport type BatchSignFlowOptions = {\n entries: BatchSignEntry[];\n onUrlReady?: (url: string) => Promise<void> | void;\n /** Total session timeout. Default 30 minutes. */\n timeoutMs?: number;\n};\n\nexport type BatchSignature = {\n signature: `0x${string}`;\n signerAddress: `0x${string}`;\n};\n\nexport type BatchSignFlowResult = {\n signatures: Map<string, BatchSignature>;\n /** Entries the user explicitly skipped or that failed (rejected wallet, etc.). */\n skipped: Map<string, string>;\n};\n\nconst DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;\n\nexport async function signBatchViaBrowser(\n opts: BatchSignFlowOptions,\n): Promise<BatchSignFlowResult> {\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const wireEntries = opts.entries.map((e) => ({\n id: e.id,\n label: e.label,\n typedData: {\n domain: ATTESTATION_DOMAIN_V1,\n message: serializeMessageForBrowser(e.message),\n primaryType: ATTESTATION_PRIMARY_TYPE,\n types: { EIP712Domain: domainTypes(), ...ATTESTATION_TYPES_V1 },\n },\n }));\n const html = buildBatchSignPageHtml(wireEntries);\n\n const signatures = new Map<string, BatchSignature>();\n const skipped = new Map<string, string>();\n\n return new Promise<BatchSignFlowResult>((resolve, reject) => {\n const server = createServer((req, res) => {\n if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n\n if (req.method === 'POST' && (req.url === '/sig' || req.url === '/skip')) {\n let body = '';\n req.on('data', (chunk: Buffer) => {\n body += chunk.toString('utf8');\n });\n req.on('end', () => {\n let parsed: {\n id: string;\n signature?: `0x${string}`;\n signerAddress?: `0x${string}`;\n reason?: string;\n };\n try {\n parsed = JSON.parse(body) as typeof parsed;\n } catch {\n res.writeHead(400);\n res.end('Parse error');\n return;\n }\n if (req.url === '/sig') {\n if (\n typeof parsed.signature !== 'string' ||\n !/^0x[0-9a-fA-F]{130}$/.test(parsed.signature) ||\n typeof parsed.signerAddress !== 'string' ||\n !/^0x[0-9a-fA-F]{40}$/.test(parsed.signerAddress)\n ) {\n res.writeHead(400);\n res.end('Invalid payload');\n return;\n }\n signatures.set(parsed.id, {\n signature: parsed.signature,\n signerAddress: parsed.signerAddress,\n });\n } else {\n skipped.set(parsed.id, parsed.reason ?? 'skipped');\n }\n res.writeHead(200, { 'content-type': 'text/plain' });\n res.end('OK');\n });\n return;\n }\n\n if (req.method === 'POST' && req.url === '/done') {\n res.writeHead(200, { 'content-type': 'text/plain' });\n res.end('OK');\n res.on('finish', () => {\n clearTimeout(timer);\n server.close();\n resolve({ signatures, skipped });\n });\n return;\n }\n\n res.writeHead(404);\n res.end('Not found');\n });\n\n const timer = setTimeout(() => {\n server.close();\n reject(new Error(`Batch sign flow timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n server.on('error', (err) => {\n clearTimeout(timer);\n reject(err);\n });\n\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address() as AddressInfo;\n const url = `http://127.0.0.1:${addr.port}/`;\n const ready = opts.onUrlReady ?? defaultOpenInBrowser;\n void Promise.resolve(ready(url)).catch((error: unknown) => {\n clearTimeout(timer);\n server.close();\n reject(error);\n });\n });\n });\n}\n\nasync function defaultOpenInBrowser(url: string): Promise<void> {\n const { default: open } = await import('open');\n await open(url);\n}\n\nfunction serializeMessageForBrowser(message: AttestationMessage): Record<string, unknown> {\n return {\n claims: {\n priorAttestation: message.claims.priorAttestation,\n publishedAt: message.claims.publishedAt.toString(),\n revision: message.claims.revision,\n slug: message.claims.slug,\n },\n schemaVersion: message.schemaVersion,\n subject: message.subject,\n };\n}\n\nfunction domainTypes(): Array<{ name: string; type: string }> {\n return [\n { name: 'name', type: 'string' },\n { name: 'version', type: 'string' },\n { name: 'chainId', type: 'uint256' },\n ];\n}\n"],"mappings":"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"}
|
package/dist/node.d.cts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { o as AttestationMessage } from "./eip712-schema.cjs";
|
|
2
|
+
//#region src/ots/stamp.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Submit a SHA-256 digest to OpenTimestamps calendars and return the proof bytes.
|
|
5
|
+
*
|
|
6
|
+
* The returned proof initially contains only calendar attestations. Run upgrade()
|
|
7
|
+
* after ~24h to anchor it in a Bitcoin block.
|
|
8
|
+
*/
|
|
9
|
+
declare function stampDigest(digest: Uint8Array): Promise<Uint8Array>;
|
|
10
|
+
/**
|
|
11
|
+
* Try to upgrade a calendar-only proof to a Bitcoin-anchored proof.
|
|
12
|
+
*
|
|
13
|
+
* Returns the (possibly upgraded) proof bytes plus a flag indicating whether
|
|
14
|
+
* the upgrade actually attached a Bitcoin attestation this time.
|
|
15
|
+
*/
|
|
16
|
+
declare function upgradeProof(otsBytes: Uint8Array): Promise<{
|
|
17
|
+
bytes: Uint8Array;
|
|
18
|
+
upgraded: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/ots/verify.d.ts
|
|
22
|
+
type OtsVerifyOk = {
|
|
23
|
+
ok: true;
|
|
24
|
+
bitcoinBlockTime: Date;
|
|
25
|
+
};
|
|
26
|
+
type OtsVerifyFail = {
|
|
27
|
+
ok: false;
|
|
28
|
+
reason: 'digest-mismatch' | 'invalid-proof' | 'pending-bitcoin';
|
|
29
|
+
details?: string;
|
|
30
|
+
};
|
|
31
|
+
type OtsVerifyResult = OtsVerifyFail | OtsVerifyOk;
|
|
32
|
+
/**
|
|
33
|
+
* Verify an OTS proof attests the given digest, and return the Bitcoin block
|
|
34
|
+
* attestation time if present.
|
|
35
|
+
*
|
|
36
|
+
* Network: this calls a public Bitcoin block-info source via the OTS library
|
|
37
|
+
* to validate the Merkle path. In production-paranoid mode you should run your
|
|
38
|
+
* own Bitcoin node and pass it explicitly (future enhancement).
|
|
39
|
+
*/
|
|
40
|
+
declare function verifyOts(digest: Uint8Array, otsBytes: Uint8Array): Promise<OtsVerifyResult>;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/eth/sign-flow.d.ts
|
|
43
|
+
type SignFlowOptions = {
|
|
44
|
+
message: AttestationMessage;
|
|
45
|
+
onUrlReady?: (url: string) => Promise<void> | void;
|
|
46
|
+
timeoutMs?: number;
|
|
47
|
+
};
|
|
48
|
+
type SignFlowResult = {
|
|
49
|
+
signature: `0x${string}`;
|
|
50
|
+
signerAddress: `0x${string}`;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Spin up a one-shot localhost HTTP server, open the browser, wait for the user
|
|
54
|
+
* to sign with their wallet, return the signature.
|
|
55
|
+
*
|
|
56
|
+
* The local server only accepts loopback requests (127.0.0.1).
|
|
57
|
+
*/
|
|
58
|
+
declare function signViaBrowser(opts: SignFlowOptions): Promise<SignFlowResult>;
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/eth/sign-batch-flow.d.ts
|
|
61
|
+
type BatchSignEntry = {
|
|
62
|
+
/** Unique key — used to correlate signatures back to entries. */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Human-readable label shown in the browser UI. */
|
|
65
|
+
label: string;
|
|
66
|
+
/** EIP-712 message to sign. */
|
|
67
|
+
message: AttestationMessage;
|
|
68
|
+
};
|
|
69
|
+
type BatchSignFlowOptions = {
|
|
70
|
+
entries: BatchSignEntry[];
|
|
71
|
+
onUrlReady?: (url: string) => Promise<void> | void;
|
|
72
|
+
/** Total session timeout. Default 30 minutes. */
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
};
|
|
75
|
+
type BatchSignature = {
|
|
76
|
+
signature: `0x${string}`;
|
|
77
|
+
signerAddress: `0x${string}`;
|
|
78
|
+
};
|
|
79
|
+
type BatchSignFlowResult = {
|
|
80
|
+
signatures: Map<string, BatchSignature>;
|
|
81
|
+
/** Entries the user explicitly skipped or that failed (rejected wallet, etc.). */
|
|
82
|
+
skipped: Map<string, string>;
|
|
83
|
+
};
|
|
84
|
+
declare function signBatchViaBrowser(opts: BatchSignFlowOptions): Promise<BatchSignFlowResult>;
|
|
85
|
+
//#endregion
|
|
86
|
+
export { type BatchSignEntry, type BatchSignFlowOptions, type BatchSignFlowResult, type BatchSignature, type OtsVerifyFail, type OtsVerifyOk, type OtsVerifyResult, type SignFlowOptions, type SignFlowResult, signBatchViaBrowser, signViaBrowser, stampDigest, upgradeProof, verifyOts };
|
|
87
|
+
//# sourceMappingURL=node.d.cts.map
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { o as AttestationMessage } from "./eip712-schema.js";
|
|
2
|
+
//#region src/ots/stamp.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Submit a SHA-256 digest to OpenTimestamps calendars and return the proof bytes.
|
|
5
|
+
*
|
|
6
|
+
* The returned proof initially contains only calendar attestations. Run upgrade()
|
|
7
|
+
* after ~24h to anchor it in a Bitcoin block.
|
|
8
|
+
*/
|
|
9
|
+
declare function stampDigest(digest: Uint8Array): Promise<Uint8Array>;
|
|
10
|
+
/**
|
|
11
|
+
* Try to upgrade a calendar-only proof to a Bitcoin-anchored proof.
|
|
12
|
+
*
|
|
13
|
+
* Returns the (possibly upgraded) proof bytes plus a flag indicating whether
|
|
14
|
+
* the upgrade actually attached a Bitcoin attestation this time.
|
|
15
|
+
*/
|
|
16
|
+
declare function upgradeProof(otsBytes: Uint8Array): Promise<{
|
|
17
|
+
bytes: Uint8Array;
|
|
18
|
+
upgraded: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/ots/verify.d.ts
|
|
22
|
+
type OtsVerifyOk = {
|
|
23
|
+
ok: true;
|
|
24
|
+
bitcoinBlockTime: Date;
|
|
25
|
+
};
|
|
26
|
+
type OtsVerifyFail = {
|
|
27
|
+
ok: false;
|
|
28
|
+
reason: 'digest-mismatch' | 'invalid-proof' | 'pending-bitcoin';
|
|
29
|
+
details?: string;
|
|
30
|
+
};
|
|
31
|
+
type OtsVerifyResult = OtsVerifyFail | OtsVerifyOk;
|
|
32
|
+
/**
|
|
33
|
+
* Verify an OTS proof attests the given digest, and return the Bitcoin block
|
|
34
|
+
* attestation time if present.
|
|
35
|
+
*
|
|
36
|
+
* Network: this calls a public Bitcoin block-info source via the OTS library
|
|
37
|
+
* to validate the Merkle path. In production-paranoid mode you should run your
|
|
38
|
+
* own Bitcoin node and pass it explicitly (future enhancement).
|
|
39
|
+
*/
|
|
40
|
+
declare function verifyOts(digest: Uint8Array, otsBytes: Uint8Array): Promise<OtsVerifyResult>;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/eth/sign-flow.d.ts
|
|
43
|
+
type SignFlowOptions = {
|
|
44
|
+
message: AttestationMessage;
|
|
45
|
+
onUrlReady?: (url: string) => Promise<void> | void;
|
|
46
|
+
timeoutMs?: number;
|
|
47
|
+
};
|
|
48
|
+
type SignFlowResult = {
|
|
49
|
+
signature: `0x${string}`;
|
|
50
|
+
signerAddress: `0x${string}`;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Spin up a one-shot localhost HTTP server, open the browser, wait for the user
|
|
54
|
+
* to sign with their wallet, return the signature.
|
|
55
|
+
*
|
|
56
|
+
* The local server only accepts loopback requests (127.0.0.1).
|
|
57
|
+
*/
|
|
58
|
+
declare function signViaBrowser(opts: SignFlowOptions): Promise<SignFlowResult>;
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/eth/sign-batch-flow.d.ts
|
|
61
|
+
type BatchSignEntry = {
|
|
62
|
+
/** Unique key — used to correlate signatures back to entries. */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Human-readable label shown in the browser UI. */
|
|
65
|
+
label: string;
|
|
66
|
+
/** EIP-712 message to sign. */
|
|
67
|
+
message: AttestationMessage;
|
|
68
|
+
};
|
|
69
|
+
type BatchSignFlowOptions = {
|
|
70
|
+
entries: BatchSignEntry[];
|
|
71
|
+
onUrlReady?: (url: string) => Promise<void> | void;
|
|
72
|
+
/** Total session timeout. Default 30 minutes. */
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
};
|
|
75
|
+
type BatchSignature = {
|
|
76
|
+
signature: `0x${string}`;
|
|
77
|
+
signerAddress: `0x${string}`;
|
|
78
|
+
};
|
|
79
|
+
type BatchSignFlowResult = {
|
|
80
|
+
signatures: Map<string, BatchSignature>;
|
|
81
|
+
/** Entries the user explicitly skipped or that failed (rejected wallet, etc.). */
|
|
82
|
+
skipped: Map<string, string>;
|
|
83
|
+
};
|
|
84
|
+
declare function signBatchViaBrowser(opts: BatchSignFlowOptions): Promise<BatchSignFlowResult>;
|
|
85
|
+
//#endregion
|
|
86
|
+
export { type BatchSignEntry, type BatchSignFlowOptions, type BatchSignFlowResult, type BatchSignature, type OtsVerifyFail, type OtsVerifyOk, type OtsVerifyResult, type SignFlowOptions, type SignFlowResult, signBatchViaBrowser, signViaBrowser, stampDigest, upgradeProof, verifyOts };
|
|
87
|
+
//# sourceMappingURL=node.d.ts.map
|