opacity-issuer-core 0.2.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/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/chunk-2STMWRIV.js +164 -0
- package/dist/chunk-2STMWRIV.js.map +1 -0
- package/dist/chunk-3L7NNVUV.cjs +184 -0
- package/dist/chunk-3L7NNVUV.cjs.map +1 -0
- package/dist/chunk-54Q3MU62.js +29 -0
- package/dist/chunk-54Q3MU62.js.map +1 -0
- package/dist/chunk-7ODYBEZH.js +889 -0
- package/dist/chunk-7ODYBEZH.js.map +1 -0
- package/dist/chunk-GMPQFX25.cjs +923 -0
- package/dist/chunk-GMPQFX25.cjs.map +1 -0
- package/dist/chunk-HJQZWNZJ.cjs +33 -0
- package/dist/chunk-HJQZWNZJ.cjs.map +1 -0
- package/dist/documents/index.cjs +170 -0
- package/dist/documents/index.cjs.map +1 -0
- package/dist/documents/index.d.cts +1370 -0
- package/dist/documents/index.d.ts +1370 -0
- package/dist/documents/index.js +5 -0
- package/dist/documents/index.js.map +1 -0
- package/dist/documents/validate.cjs +53 -0
- package/dist/documents/validate.cjs.map +1 -0
- package/dist/documents/validate.d.cts +70 -0
- package/dist/documents/validate.d.ts +70 -0
- package/dist/documents/validate.js +4 -0
- package/dist/documents/validate.js.map +1 -0
- package/dist/index.cjs +8917 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11088 -0
- package/dist/index.d.ts +11088 -0
- package/dist/index.js +8647 -0
- package/dist/index.js.map +1 -0
- package/dist/subgraph.cjs +891 -0
- package/dist/subgraph.cjs.map +1 -0
- package/dist/subgraph.d.cts +456 -0
- package/dist/subgraph.d.ts +456 -0
- package/dist/subgraph.js +882 -0
- package/dist/subgraph.js.map +1 -0
- package/dist/transport-0_X4HTI6.d.ts +39 -0
- package/dist/transport-uaC5Yl3p.d.cts +39 -0
- package/dist/types-B4Yi7WA4.d.cts +108 -0
- package/dist/types-B4Yi7WA4.d.ts +108 -0
- package/package.json +75 -0
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
import { assertDocHash, assertSessionWindow, assertAddress, assertChainId, assertNonce, DOCUMENT_ACCEPTANCE_PRIMARY_TYPE, DOCUMENT_ACCEPTANCE_TYPES, DOCUMENT_ATTESTOR_DOMAIN_VERSION, DOCUMENT_ATTESTOR_DOMAIN_NAME, EIP712_DOMAIN_TYPE, assertTraitId, assertBoolean, assertServerSignature, assertUserSignature } from './chunk-2STMWRIV.js';
|
|
2
|
+
import { OpacityAttestationError, OpacityConfigError } from './chunk-54Q3MU62.js';
|
|
3
|
+
import { keccak256, toBytes, encodeFunctionData, getAddress, hashTypedData, numberToHex, encodePacked, size, BaseError, ExecutionRevertedError, isHex, decodeFunctionResult, decodeErrorResult } from 'viem';
|
|
4
|
+
|
|
5
|
+
// src/documents/abi.ts
|
|
6
|
+
var documentAttestorAbi = [
|
|
7
|
+
{
|
|
8
|
+
type: "function",
|
|
9
|
+
name: "attestDocument",
|
|
10
|
+
stateMutability: "nonpayable",
|
|
11
|
+
inputs: [
|
|
12
|
+
{ name: "wallet", type: "address" },
|
|
13
|
+
{ name: "traitId", type: "bytes32" },
|
|
14
|
+
{ name: "docHash", type: "bytes32" },
|
|
15
|
+
{ name: "nonce", type: "bytes32" },
|
|
16
|
+
{ name: "issuedAt", type: "uint256" },
|
|
17
|
+
{ name: "deadline", type: "uint256" },
|
|
18
|
+
{ name: "serverSignature", type: "bytes" },
|
|
19
|
+
{ name: "userSignature", type: "bytes" }
|
|
20
|
+
],
|
|
21
|
+
outputs: []
|
|
22
|
+
},
|
|
23
|
+
// ── Reads ────────────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
{
|
|
25
|
+
type: "function",
|
|
26
|
+
name: "serverSigner",
|
|
27
|
+
stateMutability: "view",
|
|
28
|
+
inputs: [],
|
|
29
|
+
outputs: [{ type: "address" }]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: "function",
|
|
33
|
+
name: "usedNonces",
|
|
34
|
+
stateMutability: "view",
|
|
35
|
+
inputs: [{ name: "nonce", type: "bytes32" }],
|
|
36
|
+
outputs: [{ type: "bool" }]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: "function",
|
|
40
|
+
name: "retiredSigners",
|
|
41
|
+
stateMutability: "view",
|
|
42
|
+
inputs: [{ name: "signer", type: "address" }],
|
|
43
|
+
outputs: [{ type: "bool" }]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: "function",
|
|
47
|
+
name: "allowlistRegistry",
|
|
48
|
+
stateMutability: "view",
|
|
49
|
+
inputs: [],
|
|
50
|
+
outputs: [{ type: "address" }]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: "function",
|
|
54
|
+
name: "MAX_ATTESTATION_LIFETIME",
|
|
55
|
+
stateMutability: "view",
|
|
56
|
+
inputs: [],
|
|
57
|
+
outputs: [{ type: "uint256" }]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: "function",
|
|
61
|
+
name: "DOCUMENT_ACCEPTANCE_TYPEHASH",
|
|
62
|
+
stateMutability: "view",
|
|
63
|
+
inputs: [],
|
|
64
|
+
outputs: [{ type: "bytes32" }]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: "function",
|
|
68
|
+
name: "ATTESTATION_AUTHORIZATION_TYPEHASH",
|
|
69
|
+
stateMutability: "view",
|
|
70
|
+
inputs: [],
|
|
71
|
+
outputs: [{ type: "bytes32" }]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
// ERC-5267, inherited from OpenZeppelin's EIP712. Lets a consumer confirm the domain the SDK
|
|
75
|
+
// builds locally matches the one the deployed contract verifies against.
|
|
76
|
+
type: "function",
|
|
77
|
+
name: "eip712Domain",
|
|
78
|
+
stateMutability: "view",
|
|
79
|
+
inputs: [],
|
|
80
|
+
outputs: [
|
|
81
|
+
{ name: "fields", type: "bytes1" },
|
|
82
|
+
{ name: "name", type: "string" },
|
|
83
|
+
{ name: "version", type: "string" },
|
|
84
|
+
{ name: "chainId", type: "uint256" },
|
|
85
|
+
{ name: "verifyingContract", type: "address" },
|
|
86
|
+
{ name: "salt", type: "bytes32" },
|
|
87
|
+
{ name: "extensions", type: "uint256[]" }
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
// ── Events ───────────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
{
|
|
92
|
+
type: "event",
|
|
93
|
+
name: "DocumentAttested",
|
|
94
|
+
inputs: [
|
|
95
|
+
{ name: "userId", type: "bytes32", indexed: true },
|
|
96
|
+
{ name: "wallet", type: "address", indexed: true },
|
|
97
|
+
{ name: "traitId", type: "bytes32", indexed: true },
|
|
98
|
+
{ name: "docHash", type: "bytes32", indexed: false },
|
|
99
|
+
{ name: "nonce", type: "bytes32", indexed: false }
|
|
100
|
+
],
|
|
101
|
+
anonymous: false
|
|
102
|
+
},
|
|
103
|
+
// ── Errors: DocumentAttestor ─────────────────────────────────────────────────────────────────
|
|
104
|
+
{ type: "error", name: "ZeroAddress", inputs: [] },
|
|
105
|
+
{ type: "error", name: "InvalidTraitId", inputs: [] },
|
|
106
|
+
{ type: "error", name: "InvalidDocHash", inputs: [] },
|
|
107
|
+
{ type: "error", name: "NonceAlreadyUsed", inputs: [] },
|
|
108
|
+
{ type: "error", name: "AttestationExpired", inputs: [{ name: "deadline", type: "uint256" }] },
|
|
109
|
+
{
|
|
110
|
+
type: "error",
|
|
111
|
+
name: "AttestationDeadlineTooFar",
|
|
112
|
+
inputs: [
|
|
113
|
+
{ name: "deadline", type: "uint256" },
|
|
114
|
+
{ name: "maxAllowed", type: "uint256" }
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: "error",
|
|
119
|
+
name: "AttestationIssuedInFuture",
|
|
120
|
+
inputs: [{ name: "issuedAt", type: "uint256" }]
|
|
121
|
+
},
|
|
122
|
+
{ type: "error", name: "WalletNotOnboarded", inputs: [{ name: "wallet", type: "address" }] },
|
|
123
|
+
{ type: "error", name: "InvalidServerSignature", inputs: [] },
|
|
124
|
+
{ type: "error", name: "SignerRetired", inputs: [{ name: "signer", type: "address" }] },
|
|
125
|
+
{ type: "error", name: "InvalidUserSignature", inputs: [] },
|
|
126
|
+
// ── Errors: OpenZeppelin ECDSA, raised while recovering the server signature ─────────────────
|
|
127
|
+
{ type: "error", name: "ECDSAInvalidSignature", inputs: [] },
|
|
128
|
+
{
|
|
129
|
+
type: "error",
|
|
130
|
+
name: "ECDSAInvalidSignatureLength",
|
|
131
|
+
inputs: [{ name: "length", type: "uint256" }]
|
|
132
|
+
},
|
|
133
|
+
{ type: "error", name: "ECDSAInvalidSignatureS", inputs: [{ name: "s", type: "bytes32" }] },
|
|
134
|
+
// ── Errors: bubbled up from AllowlistRegistry.enableTraitAsWriter ────────────────────────────
|
|
135
|
+
{ type: "error", name: "NotTraitWriter", inputs: [] },
|
|
136
|
+
{ type: "error", name: "TraitNotWriterEnableable", inputs: [] },
|
|
137
|
+
{ type: "error", name: "EnforcedPause", inputs: [] }
|
|
138
|
+
];
|
|
139
|
+
function documentTraitId(name) {
|
|
140
|
+
return keccak256(toBytes(name));
|
|
141
|
+
}
|
|
142
|
+
function hashDocument(content) {
|
|
143
|
+
return keccak256(typeof content === "string" ? toBytes(content) : content);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/documents/verify.ts
|
|
147
|
+
function assertDocumentContent(content) {
|
|
148
|
+
if (typeof content !== "string" && !(content instanceof Uint8Array)) {
|
|
149
|
+
throw new OpacityAttestationError(
|
|
150
|
+
"MalformedInput",
|
|
151
|
+
"`content` must be a `Uint8Array` of document bytes or a `string` hashed as UTF-8.",
|
|
152
|
+
{ field: "content" }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (content.length === 0) {
|
|
156
|
+
throw new OpacityAttestationError(
|
|
157
|
+
"MalformedInput",
|
|
158
|
+
"`content` is empty. An executed document is never zero bytes, so this is a failed read or a render that produced nothing, rather than a document worth hashing.",
|
|
159
|
+
{ field: "content" }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return content;
|
|
163
|
+
}
|
|
164
|
+
function assertDocumentMatchesHash(content, expectedDocHash) {
|
|
165
|
+
const expected = assertDocHash(expectedDocHash);
|
|
166
|
+
const actual = hashDocument(assertDocumentContent(content));
|
|
167
|
+
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
168
|
+
throw new OpacityAttestationError(
|
|
169
|
+
"DocHashMismatch",
|
|
170
|
+
`The rendered document does not match the session \`docHash\`. The session commits to ${expected}, but the supplied content hashes to ${actual}. Do not raise the wallet prompt \u2014 the investor would be signing for a document they were never shown. The client is the source of this hash, so either the session echoed back a different \`docHash\` than the one posted to \`POST /attestations\`, or these are not the bytes that were hashed for it: a template, a re-encoded PDF, or a truncated read each hash differently. Re-hash the exact executed artifact that was submitted; if it still does not match, abandon the session and escalate \u2014 the backend is not committing to the document being signed.`,
|
|
171
|
+
{ field: "docHash" }
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function toUnixSeconds(value, field) {
|
|
176
|
+
if (typeof value === "bigint") return value;
|
|
177
|
+
if (typeof value === "number") {
|
|
178
|
+
if (!Number.isSafeInteger(value)) {
|
|
179
|
+
throw new OpacityAttestationError(
|
|
180
|
+
"MalformedInput",
|
|
181
|
+
`\`${field}\` must be a whole number of unix seconds; received ${value}.`,
|
|
182
|
+
{ field }
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return BigInt(value);
|
|
186
|
+
}
|
|
187
|
+
if (typeof value === "string" && /^[0-9]+$/.test(value)) {
|
|
188
|
+
return BigInt(value);
|
|
189
|
+
}
|
|
190
|
+
throw new OpacityAttestationError(
|
|
191
|
+
"MalformedInput",
|
|
192
|
+
`\`${field}\` must be unix seconds as a bigint, a whole number, or a decimal string.`,
|
|
193
|
+
{ field }
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function asRecord(value) {
|
|
197
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
198
|
+
}
|
|
199
|
+
function isTimestampLike(value) {
|
|
200
|
+
return typeof value === "bigint" || typeof value === "number" || typeof value === "string";
|
|
201
|
+
}
|
|
202
|
+
function readSessionFields(session) {
|
|
203
|
+
const record = asRecord(session);
|
|
204
|
+
if (record === void 0) {
|
|
205
|
+
throw new OpacityAttestationError(
|
|
206
|
+
"MalformedInput",
|
|
207
|
+
"`session` must be the object returned by `POST /attestations`.",
|
|
208
|
+
{ field: "session" }
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const message = asRecord(asRecord(record["typedData"])?.["message"]);
|
|
212
|
+
return {
|
|
213
|
+
docHash: readSessionDocHash(message?.["docHash"], record["docHash"]),
|
|
214
|
+
deadline: readSessionDeadline(record["deadline"], message?.["deadline"])
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function readSessionDocHash(signed, echoed) {
|
|
218
|
+
const signedHash = typeof signed === "string" ? signed : void 0;
|
|
219
|
+
const echoedHash = typeof echoed === "string" ? echoed : void 0;
|
|
220
|
+
if (signedHash !== void 0 && echoedHash !== void 0 && signedHash.toLowerCase() !== echoedHash.toLowerCase()) {
|
|
221
|
+
throw new OpacityAttestationError(
|
|
222
|
+
"DocHashMismatch",
|
|
223
|
+
`The session disagrees with itself about \`docHash\`: the top-level field says ${echoedHash} while \`typedData.message\`, the copy the wallet actually signs, says ${signedHash}. A UI that displays one and prompts for the other is exactly the gap this check exists to close. Abandon the session and escalate.`,
|
|
224
|
+
{ field: "docHash" }
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const docHash = signedHash ?? echoedHash;
|
|
228
|
+
if (docHash === void 0) {
|
|
229
|
+
throw new OpacityAttestationError(
|
|
230
|
+
"MalformedInput",
|
|
231
|
+
"`session` carries no usable `docHash`. It is read from `typedData.message.docHash`, falling back to the top-level `docHash`.",
|
|
232
|
+
{ field: "docHash" }
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return docHash;
|
|
236
|
+
}
|
|
237
|
+
function readSessionDeadline(top, signed) {
|
|
238
|
+
const topDeadline = isTimestampLike(top) ? toUnixSeconds(top, "deadline") : void 0;
|
|
239
|
+
const signedDeadline = isTimestampLike(signed) ? toUnixSeconds(signed, "deadline") : void 0;
|
|
240
|
+
if (topDeadline !== void 0 && signedDeadline !== void 0 && topDeadline !== signedDeadline) {
|
|
241
|
+
throw new OpacityAttestationError(
|
|
242
|
+
"MalformedInput",
|
|
243
|
+
`The session disagrees with itself about \`deadline\`: the top level says ${topDeadline} while \`typedData.message\`, the copy the wallet signs and the contract enforces, says ${signedDeadline}.`,
|
|
244
|
+
{ field: "deadline" }
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const deadline = topDeadline ?? signedDeadline;
|
|
248
|
+
if (deadline === void 0) {
|
|
249
|
+
throw new OpacityAttestationError(
|
|
250
|
+
"MalformedInput",
|
|
251
|
+
"`session` carries no usable `deadline`. It is read from the top-level `deadline`, falling back to `typedData.message.deadline`.",
|
|
252
|
+
{ field: "deadline" }
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return deadline;
|
|
256
|
+
}
|
|
257
|
+
function verifyAttestationSession(params) {
|
|
258
|
+
const { docHash, deadline } = "session" in params ? readSessionFields(params.session) : { docHash: params.docHash, deadline: toUnixSeconds(params.deadline, "deadline") };
|
|
259
|
+
assertDocumentMatchesHash(params.content, docHash);
|
|
260
|
+
assertSessionWindow(
|
|
261
|
+
{
|
|
262
|
+
issuedAt: params.issuedAt === void 0 ? void 0 : toUnixSeconds(params.issuedAt, "issuedAt"),
|
|
263
|
+
deadline
|
|
264
|
+
},
|
|
265
|
+
params.now === void 0 ? {} : { now: params.now }
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
function buildDocumentAcceptanceTypedData(params) {
|
|
269
|
+
const verifyingContract = assertAddress(params.attestorAddress, "attestorAddress");
|
|
270
|
+
const chainId = assertChainId(params.chainId);
|
|
271
|
+
const nonce = assertNonce(params.nonce);
|
|
272
|
+
const docHash = assertDocHash(params.docHash);
|
|
273
|
+
assertSessionWindow(
|
|
274
|
+
{ issuedAt: params.issuedAt, deadline: params.deadline },
|
|
275
|
+
params.now === void 0 ? {} : { now: params.now }
|
|
276
|
+
);
|
|
277
|
+
return {
|
|
278
|
+
domain: {
|
|
279
|
+
name: DOCUMENT_ATTESTOR_DOMAIN_NAME,
|
|
280
|
+
version: DOCUMENT_ATTESTOR_DOMAIN_VERSION,
|
|
281
|
+
chainId,
|
|
282
|
+
verifyingContract
|
|
283
|
+
},
|
|
284
|
+
types: DOCUMENT_ACCEPTANCE_TYPES,
|
|
285
|
+
primaryType: DOCUMENT_ACCEPTANCE_PRIMARY_TYPE,
|
|
286
|
+
message: { nonce, docHash, deadline: params.deadline }
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function hashDocumentAcceptance(typedData) {
|
|
290
|
+
return hashTypedData(typedData);
|
|
291
|
+
}
|
|
292
|
+
function toEthersTypedData(typedData) {
|
|
293
|
+
return {
|
|
294
|
+
domain: typedData.domain,
|
|
295
|
+
types: {
|
|
296
|
+
DocumentAcceptance: typedData.types.DocumentAcceptance.map((field) => ({ ...field }))
|
|
297
|
+
},
|
|
298
|
+
value: typedData.message
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function toEip712JsonPayload(typedData) {
|
|
302
|
+
return {
|
|
303
|
+
types: {
|
|
304
|
+
EIP712Domain: EIP712_DOMAIN_TYPE,
|
|
305
|
+
...typedData.types
|
|
306
|
+
},
|
|
307
|
+
primaryType: typedData.primaryType,
|
|
308
|
+
domain: {
|
|
309
|
+
name: typedData.domain.name,
|
|
310
|
+
version: typedData.domain.version,
|
|
311
|
+
chainId: typedData.domain.chainId,
|
|
312
|
+
verifyingContract: typedData.domain.verifyingContract
|
|
313
|
+
},
|
|
314
|
+
message: {
|
|
315
|
+
nonce: typedData.message.nonce,
|
|
316
|
+
docHash: typedData.message.docHash,
|
|
317
|
+
deadline: typedData.message.deadline.toString()
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function toEip712Json(typedData) {
|
|
322
|
+
return JSON.stringify(toEip712JsonPayload(typedData));
|
|
323
|
+
}
|
|
324
|
+
function toArgs(params) {
|
|
325
|
+
const wallet = assertAddress(params.wallet, "wallet");
|
|
326
|
+
const traitId = assertTraitId(params.traitId);
|
|
327
|
+
const docHash = assertDocHash(params.docHash);
|
|
328
|
+
const nonce = assertNonce(params.nonce);
|
|
329
|
+
const serverSignature = assertServerSignature(params.serverSignature);
|
|
330
|
+
const userSignature = assertUserSignature(params.userSignature);
|
|
331
|
+
assertSessionWindow(
|
|
332
|
+
{ issuedAt: params.issuedAt, deadline: params.deadline },
|
|
333
|
+
params.now === void 0 ? {} : { now: params.now }
|
|
334
|
+
);
|
|
335
|
+
return [
|
|
336
|
+
wallet,
|
|
337
|
+
traitId,
|
|
338
|
+
docHash,
|
|
339
|
+
nonce,
|
|
340
|
+
params.issuedAt,
|
|
341
|
+
params.deadline,
|
|
342
|
+
serverSignature,
|
|
343
|
+
userSignature
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
function buildAttestDocumentTransaction(params) {
|
|
347
|
+
const to = assertAddress(params.attestorAddress, "attestorAddress");
|
|
348
|
+
const chainId = assertChainId(params.chainId);
|
|
349
|
+
const from = params.from === void 0 ? void 0 : assertAddress(params.from, "from");
|
|
350
|
+
const request = {
|
|
351
|
+
to,
|
|
352
|
+
data: encodeFunctionData({
|
|
353
|
+
abi: documentAttestorAbi,
|
|
354
|
+
functionName: "attestDocument",
|
|
355
|
+
args: toArgs(params)
|
|
356
|
+
}),
|
|
357
|
+
value: 0n,
|
|
358
|
+
chainId
|
|
359
|
+
};
|
|
360
|
+
return from === void 0 ? request : { ...request, from };
|
|
361
|
+
}
|
|
362
|
+
function toWriteContractRequest(params) {
|
|
363
|
+
return {
|
|
364
|
+
address: assertAddress(params.attestorAddress, "attestorAddress"),
|
|
365
|
+
abi: documentAttestorAbi,
|
|
366
|
+
functionName: "attestDocument",
|
|
367
|
+
args: toArgs(params),
|
|
368
|
+
chainId: assertChainId(params.chainId)
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function toJsonRpcTransaction(transaction, options) {
|
|
372
|
+
return {
|
|
373
|
+
from: assertAddress(options.from, "from"),
|
|
374
|
+
to: transaction.to,
|
|
375
|
+
data: transaction.data,
|
|
376
|
+
value: numberToHex(transaction.value),
|
|
377
|
+
chainId: numberToHex(transaction.chainId)
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
var sandboxAllowlistRegistryAbi = [
|
|
381
|
+
{
|
|
382
|
+
type: "function",
|
|
383
|
+
name: "sandboxRegisterIssuer",
|
|
384
|
+
stateMutability: "nonpayable",
|
|
385
|
+
inputs: [],
|
|
386
|
+
outputs: []
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
type: "function",
|
|
390
|
+
name: "sandboxAddSelf",
|
|
391
|
+
stateMutability: "nonpayable",
|
|
392
|
+
inputs: [],
|
|
393
|
+
outputs: []
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
type: "function",
|
|
397
|
+
name: "sandboxSetSelfTrait",
|
|
398
|
+
stateMutability: "nonpayable",
|
|
399
|
+
inputs: [
|
|
400
|
+
{ name: "traitId", type: "bytes32" },
|
|
401
|
+
{ name: "enabled", type: "bool" }
|
|
402
|
+
],
|
|
403
|
+
outputs: []
|
|
404
|
+
},
|
|
405
|
+
// ── Marker ───────────────────────────────────────────────────────────────────────────────────
|
|
406
|
+
//
|
|
407
|
+
// Present ONLY on the sandbox implementation, so tooling can tell it apart from the stock
|
|
408
|
+
// `AllowlistRegistry` behind a proxy with one staticcall instead of a storage probe. The marker
|
|
409
|
+
// landed as version 1 in contracts commit 281e9c5; the self-trait surface advances it to 2.
|
|
410
|
+
// Sandbox v3 adds issuer self-registration. Older implementations deployed before the marker
|
|
411
|
+
// revert on it while `sandboxAddSelf()` works; `probeSandboxRegistry` copes with both.
|
|
412
|
+
{
|
|
413
|
+
type: "function",
|
|
414
|
+
name: "sandboxVersion",
|
|
415
|
+
stateMutability: "pure",
|
|
416
|
+
inputs: [],
|
|
417
|
+
outputs: [{ type: "uint256" }]
|
|
418
|
+
},
|
|
419
|
+
// ── Reads ────────────────────────────────────────────────────────────────────────────────────
|
|
420
|
+
{
|
|
421
|
+
type: "function",
|
|
422
|
+
name: "isAllowed",
|
|
423
|
+
stateMutability: "view",
|
|
424
|
+
inputs: [{ name: "account", type: "address" }],
|
|
425
|
+
outputs: [{ type: "bool" }]
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
type: "function",
|
|
429
|
+
name: "getUserId",
|
|
430
|
+
stateMutability: "view",
|
|
431
|
+
inputs: [{ name: "account", type: "address" }],
|
|
432
|
+
outputs: [{ type: "bytes32" }]
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
type: "function",
|
|
436
|
+
name: "hasGlobalTrait",
|
|
437
|
+
stateMutability: "view",
|
|
438
|
+
inputs: [
|
|
439
|
+
{ name: "userId", type: "bytes32" },
|
|
440
|
+
{ name: "traitId", type: "bytes32" }
|
|
441
|
+
],
|
|
442
|
+
outputs: [{ type: "bool" }]
|
|
443
|
+
},
|
|
444
|
+
// ── Events ───────────────────────────────────────────────────────────────────────────────────
|
|
445
|
+
{
|
|
446
|
+
type: "event",
|
|
447
|
+
name: "GlobalTraitSet",
|
|
448
|
+
anonymous: false,
|
|
449
|
+
inputs: [
|
|
450
|
+
{ name: "userId", type: "bytes32", indexed: true },
|
|
451
|
+
{ name: "traitId", type: "bytes32", indexed: true },
|
|
452
|
+
{ name: "enabled", type: "bool", indexed: false },
|
|
453
|
+
{ name: "updatedBy", type: "address", indexed: false }
|
|
454
|
+
]
|
|
455
|
+
},
|
|
456
|
+
// ── Errors reachable from the sandbox writes ─────────────────────────────────────────────────
|
|
457
|
+
{ type: "error", name: "EnforcedPause", inputs: [] },
|
|
458
|
+
{ type: "error", name: "AddressNotEligible", inputs: [{ name: "account", type: "address" }] },
|
|
459
|
+
{ type: "error", name: "AddressNotVerified", inputs: [{ name: "account", type: "address" }] },
|
|
460
|
+
{ type: "error", name: "InvalidUserId", inputs: [] },
|
|
461
|
+
{ type: "error", name: "InvalidTraitId", inputs: [] }
|
|
462
|
+
];
|
|
463
|
+
var SANDBOX_IMPLEMENTATION_VERSION = 3n;
|
|
464
|
+
var SANDBOX_TRAIT_IDS = Object.freeze({
|
|
465
|
+
/** `keccak256("trait.accredited")` — the contracts' canonical accredited-investor trait. */
|
|
466
|
+
accreditedInvestor: keccak256(toBytes("trait.accredited")),
|
|
467
|
+
/** `keccak256("doc.mmf.subscription.v1")` — the current MMF subscription document trait. */
|
|
468
|
+
mmfDocumentSigned: keccak256(toBytes("doc.mmf.subscription.v1"))
|
|
469
|
+
});
|
|
470
|
+
function buildSandboxAddSelfTransaction(params) {
|
|
471
|
+
const to = assertAddress(params.registryAddress, "registryAddress");
|
|
472
|
+
const chainId = assertChainId(params.chainId);
|
|
473
|
+
const from = params.from === void 0 ? void 0 : assertAddress(params.from, "from");
|
|
474
|
+
const request = {
|
|
475
|
+
to,
|
|
476
|
+
data: SANDBOX_ADD_SELF_CALLDATA,
|
|
477
|
+
value: 0n,
|
|
478
|
+
chainId
|
|
479
|
+
};
|
|
480
|
+
return from === void 0 ? request : { ...request, from };
|
|
481
|
+
}
|
|
482
|
+
function toSandboxAddSelfWriteContractRequest(params) {
|
|
483
|
+
const address = assertAddress(params.registryAddress, "registryAddress");
|
|
484
|
+
const chainId = assertChainId(params.chainId);
|
|
485
|
+
const account = params.from === void 0 ? void 0 : assertAddress(params.from, "from");
|
|
486
|
+
const request = {
|
|
487
|
+
address,
|
|
488
|
+
abi: sandboxAllowlistRegistryAbi,
|
|
489
|
+
functionName: "sandboxAddSelf",
|
|
490
|
+
args: [],
|
|
491
|
+
chainId
|
|
492
|
+
};
|
|
493
|
+
return account === void 0 ? request : { ...request, account };
|
|
494
|
+
}
|
|
495
|
+
function buildSandboxRegisterIssuerTransaction(params) {
|
|
496
|
+
return {
|
|
497
|
+
...buildSandboxAddSelfTransaction(params),
|
|
498
|
+
data: encodeFunctionData({
|
|
499
|
+
abi: sandboxAllowlistRegistryAbi,
|
|
500
|
+
functionName: "sandboxRegisterIssuer"
|
|
501
|
+
})
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function toSandboxRegisterIssuerWriteContractRequest(params) {
|
|
505
|
+
return { ...toSandboxAddSelfWriteContractRequest(params), functionName: "sandboxRegisterIssuer" };
|
|
506
|
+
}
|
|
507
|
+
function buildSandboxSetSelfTraitTransaction(params) {
|
|
508
|
+
const to = assertAddress(params.registryAddress, "registryAddress");
|
|
509
|
+
const chainId = assertChainId(params.chainId);
|
|
510
|
+
const traitId = assertTraitId(params.traitId);
|
|
511
|
+
const enabled = assertBoolean(params.enabled, "enabled");
|
|
512
|
+
const from = params.from === void 0 ? void 0 : assertAddress(params.from, "from");
|
|
513
|
+
const request = {
|
|
514
|
+
to,
|
|
515
|
+
data: encodeFunctionData({
|
|
516
|
+
abi: sandboxAllowlistRegistryAbi,
|
|
517
|
+
functionName: "sandboxSetSelfTrait",
|
|
518
|
+
args: [traitId, enabled]
|
|
519
|
+
}),
|
|
520
|
+
value: 0n,
|
|
521
|
+
chainId
|
|
522
|
+
};
|
|
523
|
+
return from === void 0 ? request : { ...request, from };
|
|
524
|
+
}
|
|
525
|
+
function toSandboxSetSelfTraitWriteContractRequest(params) {
|
|
526
|
+
const address = assertAddress(params.registryAddress, "registryAddress");
|
|
527
|
+
const chainId = assertChainId(params.chainId);
|
|
528
|
+
const traitId = assertTraitId(params.traitId);
|
|
529
|
+
const enabled = assertBoolean(params.enabled, "enabled");
|
|
530
|
+
const account = params.from === void 0 ? void 0 : assertAddress(params.from, "from");
|
|
531
|
+
const request = {
|
|
532
|
+
address,
|
|
533
|
+
abi: sandboxAllowlistRegistryAbi,
|
|
534
|
+
functionName: "sandboxSetSelfTrait",
|
|
535
|
+
args: [traitId, enabled],
|
|
536
|
+
chainId
|
|
537
|
+
};
|
|
538
|
+
return account === void 0 ? request : { ...request, account };
|
|
539
|
+
}
|
|
540
|
+
function sandboxUserId(wallet) {
|
|
541
|
+
const normalized = assertAddress(wallet, "wallet");
|
|
542
|
+
return keccak256(encodePacked(["string", "address"], ["sandbox.user.", normalized]));
|
|
543
|
+
}
|
|
544
|
+
var SANDBOX_ADD_SELF_CALLDATA = encodeFunctionData({
|
|
545
|
+
abi: sandboxAllowlistRegistryAbi,
|
|
546
|
+
functionName: "sandboxAddSelf",
|
|
547
|
+
args: []
|
|
548
|
+
});
|
|
549
|
+
var SANDBOX_VERSION_CALLDATA = encodeFunctionData({
|
|
550
|
+
abi: sandboxAllowlistRegistryAbi,
|
|
551
|
+
functionName: "sandboxVersion",
|
|
552
|
+
args: []
|
|
553
|
+
});
|
|
554
|
+
var PROBE_SENDER = "0x1000000000000000000000000000000000000001";
|
|
555
|
+
async function probeSandboxRegistry(client, params) {
|
|
556
|
+
const registryAddress = assertAddress(params.registryAddress, "registryAddress");
|
|
557
|
+
const expectedChainId = assertChainId(params.chainId);
|
|
558
|
+
const from = params.from === void 0 ? PROBE_SENDER : assertAddress(params.from, "from");
|
|
559
|
+
const actualChainId = await client.getChainId();
|
|
560
|
+
if (actualChainId !== expectedChainId) {
|
|
561
|
+
throw new OpacityConfigError(
|
|
562
|
+
`Sandbox registry ${registryAddress} was resolved for chain ${expectedChainId}, but the probe RPC client is connected to chain ${actualChainId}. Pass a public client for chain ${expectedChainId}.`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
const code = await client.getBytecode({ address: registryAddress });
|
|
566
|
+
if (code === void 0 || size(code) === 0) {
|
|
567
|
+
return { registryAddress, attached: false, version: null, evidence: "no_code" };
|
|
568
|
+
}
|
|
569
|
+
const marker = await tryCall(client, { to: registryAddress, data: SANDBOX_VERSION_CALLDATA });
|
|
570
|
+
if (marker.outcome === "returned") {
|
|
571
|
+
if (marker.data === void 0 || size(marker.data) === 0) {
|
|
572
|
+
return { registryAddress, attached: false, version: null, evidence: "not_sandbox" };
|
|
573
|
+
}
|
|
574
|
+
const version = decodeVersion(marker.data);
|
|
575
|
+
return version === void 0 ? { registryAddress, attached: false, version: null, evidence: "not_sandbox" } : { registryAddress, attached: true, version, evidence: "marker" };
|
|
576
|
+
}
|
|
577
|
+
const legacy = await tryCall(client, {
|
|
578
|
+
to: registryAddress,
|
|
579
|
+
data: SANDBOX_ADD_SELF_CALLDATA,
|
|
580
|
+
account: from
|
|
581
|
+
});
|
|
582
|
+
if (legacy.outcome === "returned") {
|
|
583
|
+
return legacy.data === void 0 || size(legacy.data) === 0 ? { registryAddress, attached: true, version: null, evidence: "self_add_call" } : { registryAddress, attached: false, version: null, evidence: "not_sandbox" };
|
|
584
|
+
}
|
|
585
|
+
if (legacy.data !== void 0 && isSandboxRegistryError(legacy.data)) {
|
|
586
|
+
return { registryAddress, attached: true, version: null, evidence: "self_add_revert" };
|
|
587
|
+
}
|
|
588
|
+
return { registryAddress, attached: false, version: null, evidence: "not_sandbox" };
|
|
589
|
+
}
|
|
590
|
+
async function tryCall(client, args) {
|
|
591
|
+
try {
|
|
592
|
+
const { data } = await client.call(args);
|
|
593
|
+
return { outcome: "returned", data };
|
|
594
|
+
} catch (error) {
|
|
595
|
+
const revert = asExecutionRevert(error);
|
|
596
|
+
if (revert === void 0) throw error;
|
|
597
|
+
return { outcome: "reverted", data: revert.data };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function asExecutionRevert(error) {
|
|
601
|
+
const root = error instanceof BaseError ? error.walk() : error;
|
|
602
|
+
const reverted = error instanceof BaseError && error.walk((e) => e instanceof ExecutionRevertedError) !== null || hasRevertCode(root) || hasRevertMessage(root) || hasRevertMessage(error);
|
|
603
|
+
if (!reverted) return void 0;
|
|
604
|
+
return { data: revertData(root) ?? revertData(error) };
|
|
605
|
+
}
|
|
606
|
+
function hasRevertCode(value) {
|
|
607
|
+
return typeof value === "object" && value !== null && value.code === ExecutionRevertedError.code;
|
|
608
|
+
}
|
|
609
|
+
function hasRevertMessage(value) {
|
|
610
|
+
if (typeof value !== "object" || value === null) return false;
|
|
611
|
+
const record = value;
|
|
612
|
+
return [record.message, record.details, record.shortMessage].some(
|
|
613
|
+
(text) => typeof text === "string" && ExecutionRevertedError.nodeMessage.test(text.toLowerCase())
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
function revertData(value) {
|
|
617
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
618
|
+
const data = value.data;
|
|
619
|
+
if (typeof data === "string" && isHex(data)) return data;
|
|
620
|
+
if (typeof data === "object" && data !== null) {
|
|
621
|
+
const nested = data.data;
|
|
622
|
+
if (typeof nested === "string" && isHex(nested)) return nested;
|
|
623
|
+
}
|
|
624
|
+
return void 0;
|
|
625
|
+
}
|
|
626
|
+
function decodeVersion(data) {
|
|
627
|
+
if (size(data) !== 32) return void 0;
|
|
628
|
+
try {
|
|
629
|
+
const version = decodeFunctionResult({
|
|
630
|
+
abi: sandboxAllowlistRegistryAbi,
|
|
631
|
+
functionName: "sandboxVersion",
|
|
632
|
+
data
|
|
633
|
+
});
|
|
634
|
+
return version > 0n ? version : void 0;
|
|
635
|
+
} catch {
|
|
636
|
+
return void 0;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function isSandboxRegistryError(data) {
|
|
640
|
+
try {
|
|
641
|
+
const decoded = decodeErrorResult({ abi: sandboxAllowlistRegistryAbi, data });
|
|
642
|
+
return decoded.errorName === "EnforcedPause" || decoded.errorName === "AddressNotEligible";
|
|
643
|
+
} catch {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
var SEPOLIA_SANDBOX_MANIFEST = Object.freeze({
|
|
648
|
+
chainId: 11155111,
|
|
649
|
+
contractsCommit: "ee48b026312447f4b98259c5b69c5b3dd909e7ac",
|
|
650
|
+
startBlock: 11704186,
|
|
651
|
+
deploymentEndBlock: 11704251,
|
|
652
|
+
seedEndBlock: 11704261,
|
|
653
|
+
verifiedAtBlock: 11704261,
|
|
654
|
+
verifiedAtBlockHash: "0x2be898f65c42951b74f5638840b580f50a3d67e6a3fdacb23a93c91d5d30e584",
|
|
655
|
+
sandboxVersion: 3,
|
|
656
|
+
contractsPr: "https://github.com/OpacityLabs/pilot-evm/pull/163",
|
|
657
|
+
mmfTokenId: 1n,
|
|
658
|
+
defaultBeaconVariant: "0x9710c10589ba16449491db61cd49910e5850d89fd101354fd502e443315ef1a7",
|
|
659
|
+
documentTraitId: "0x07a0e2ce63cbf806e2e785550e9037052acefbf71086a0b714149f62d2511603",
|
|
660
|
+
addresses: Object.freeze({
|
|
661
|
+
registry: getAddress("0x4f471f8719192965c7ee1c3ab460d55d9734ac17"),
|
|
662
|
+
registryImplementation: getAddress("0x580ee27e862052c3454e99be374785394b490e5c"),
|
|
663
|
+
securityTokenImplementation: getAddress("0xf388b72cc6043c97f33e2bd74c73b102efc0540a"),
|
|
664
|
+
timelockBeacon: getAddress("0x8362deb9ddde9a37cc104ac5f567596d295e9f2f"),
|
|
665
|
+
beaconRegistry: getAddress("0xc7e3f9802e637cd63096008cc2fb0e4b5da91c79"),
|
|
666
|
+
factoryImplementation: getAddress("0x5b0db45654ac926405c270de0947767ef8edde12"),
|
|
667
|
+
policyRegistry: getAddress("0x23f059b4a2962b2801804b69db78200cc4ca5308"),
|
|
668
|
+
policyEngineImplementation: getAddress("0x7b1bc77a3934b7c04aebfa783ae1c9047962fe80"),
|
|
669
|
+
orchestratorImplementation: getAddress("0x1c3dc8c8e87a1e2840283f1418b3787d3825f492"),
|
|
670
|
+
securityTokenLens: getAddress("0xa3d006def1b1330a52776271cc43e1c76d505c59"),
|
|
671
|
+
overrideHelperImplementation: getAddress("0x0d9c174129bab0c6005ad228a5457f53dcebf3c5"),
|
|
672
|
+
tokenTraitRegistry: getAddress("0x5d491bc34df171e0657e26911bd6821b8257d139"),
|
|
673
|
+
documentAttestor: getAddress("0x0de5ac323a4756e44620b2da5845fe8c3bdcc229"),
|
|
674
|
+
mockUSDC: getAddress("0x88043845880c5a23ba54bcbf68570c882d3e6ae5"),
|
|
675
|
+
vault: getAddress("0xfb8a78e328c0d8894cd3c87244d67e79e7d0c6af"),
|
|
676
|
+
factory: getAddress("0xb5554c9ea27d3797cdd7b9582098bedd8e487213"),
|
|
677
|
+
policyEngine: getAddress("0x1fc164c13cdd0fb72a68b7ab29f141446613b691"),
|
|
678
|
+
orchestrator: getAddress("0x6378a86ff255107df40a8c442710a3f2dea14ded"),
|
|
679
|
+
overrideHelper: getAddress("0x47f535716d1115bb5416317b784819c934c3ced5"),
|
|
680
|
+
helperTimelock: getAddress("0xcca60edb282d18dec3d92515d2fc328e981cf56e"),
|
|
681
|
+
engineTimelock: getAddress("0x74d8a771245b836b1bf5811812025d11d056e371"),
|
|
682
|
+
orchestratorTimelock: getAddress("0x48d66185eb49999d21db44ccf5a67dc81ace2953"),
|
|
683
|
+
factoryTimelock: getAddress("0x70b081d01b7dda17e5beaf66b006e58dc18911a1"),
|
|
684
|
+
registryProxyAdmin: getAddress("0x6bd7489a9882dbf7102dc173e770963ba626f179"),
|
|
685
|
+
innerBeacon: getAddress("0x5d0d62913f3e347bfbb3289b1e74c8a3a2823201"),
|
|
686
|
+
mmfToken: getAddress("0x490b0b9396cb1b1ee717acd1376c3f21de4ee808")
|
|
687
|
+
}),
|
|
688
|
+
policies: Object.freeze({
|
|
689
|
+
TradingDisabledPolicy: getAddress("0xba5798564c819f886f93aad71a074ebc874a19e3"),
|
|
690
|
+
AllowedTransfereeTypePolicy: getAddress("0x2645026dc2eb3233bce7ead874715a485984bbc6"),
|
|
691
|
+
HolderCountCapPolicy: getAddress("0x631ccf8d3a44e5b03df176d915b09d30f34c94c3"),
|
|
692
|
+
FloorBalancePolicy: getAddress("0xcc33aa099db0cdadf0e124f9aa64cb130f0bf513"),
|
|
693
|
+
CeilingBalancePolicy: getAddress("0xb4806408c9a80442ed277df4cb33a7e6b69d1d08"),
|
|
694
|
+
DenylistPolicy: getAddress("0xfedf6f0df8473ba96bc2c379f9a41b1947a53af7"),
|
|
695
|
+
InsiderTradingFreezePolicy: getAddress("0xd560c8fb46f9d61e1067177c9ccb09ba0a4c4489"),
|
|
696
|
+
SelfTransferOnlyPolicy: getAddress("0xc1bf4aad716407984e020b38c27b165f0716265a")
|
|
697
|
+
})
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// src/documents/sandbox-deployments.ts
|
|
701
|
+
var SEPOLIA_CHAIN_ID = 11155111;
|
|
702
|
+
var SANDBOX_CHAIN_ID = SEPOLIA_CHAIN_ID;
|
|
703
|
+
var SANDBOX_DEPLOYMENTS = Object.freeze({
|
|
704
|
+
[SEPOLIA_CHAIN_ID]: Object.freeze({
|
|
705
|
+
chainId: SEPOLIA_CHAIN_ID,
|
|
706
|
+
allowlistRegistry: SEPOLIA_SANDBOX_MANIFEST.addresses.registry,
|
|
707
|
+
documentAttestor: SEPOLIA_SANDBOX_MANIFEST.addresses.documentAttestor
|
|
708
|
+
})
|
|
709
|
+
});
|
|
710
|
+
function getSandboxDeployment(chainId) {
|
|
711
|
+
return SANDBOX_DEPLOYMENTS[chainId];
|
|
712
|
+
}
|
|
713
|
+
function requireSandboxDeployment(chainId) {
|
|
714
|
+
const deployment = getSandboxDeployment(chainId);
|
|
715
|
+
if (deployment !== void 0) return deployment;
|
|
716
|
+
const known = Object.keys(SANDBOX_DEPLOYMENTS).join(", ");
|
|
717
|
+
throw new OpacityConfigError(
|
|
718
|
+
`No built-in sandbox deployment for chain ${chainId}. This SDK ships sandbox addresses for chain(s) ${known} only. Point the client at one of those, pass \`registryAddress\` explicitly, or rely on discovery (\`GET /chains/:chain_id/attestor\`) for a chain whose backend serves one.`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/documents/adapters.ts
|
|
723
|
+
function toViemTypedData(typedData, options = {}) {
|
|
724
|
+
const record = asRecord2(typedData);
|
|
725
|
+
if (record === void 0) {
|
|
726
|
+
throw typedDataError("`typedData` is not an object.", "typedData");
|
|
727
|
+
}
|
|
728
|
+
if (record["primaryType"] !== DOCUMENT_ACCEPTANCE_PRIMARY_TYPE) {
|
|
729
|
+
throw typedDataError(
|
|
730
|
+
`\`primaryType\` must be \`${DOCUMENT_ACCEPTANCE_PRIMARY_TYPE}\`; received ${describe(record["primaryType"])}.`,
|
|
731
|
+
"typedData.primaryType"
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
const types = asRecord2(record["types"]);
|
|
735
|
+
assertAcceptanceFields(types?.[DOCUMENT_ACCEPTANCE_PRIMARY_TYPE]);
|
|
736
|
+
assertDomainFieldsIfDeclared(types?.[EIP712_DOMAIN_TYPE_NAME]);
|
|
737
|
+
const domain = asRecord2(record["domain"]);
|
|
738
|
+
if (domain === void 0) {
|
|
739
|
+
throw typedDataError("`domain` is missing or is not an object.", "typedData.domain");
|
|
740
|
+
}
|
|
741
|
+
if (domain["name"] !== DOCUMENT_ATTESTOR_DOMAIN_NAME) {
|
|
742
|
+
throw typedDataError(
|
|
743
|
+
`\`domain.name\` must be \`${DOCUMENT_ATTESTOR_DOMAIN_NAME}\`; received ${describe(domain["name"])}. A different domain name is a different contract.`,
|
|
744
|
+
"typedData.domain.name"
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
if (domain["version"] !== DOCUMENT_ATTESTOR_DOMAIN_VERSION) {
|
|
748
|
+
throw typedDataError(
|
|
749
|
+
`\`domain.version\` must be \`${DOCUMENT_ATTESTOR_DOMAIN_VERSION}\`; received ${describe(domain["version"])}.`,
|
|
750
|
+
"typedData.domain.version"
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
const chainId = assertChainId(asNumber(domain["chainId"]) ?? Number.NaN);
|
|
754
|
+
const verifyingContract = assertAddress(
|
|
755
|
+
domain["verifyingContract"],
|
|
756
|
+
"domain.verifyingContract"
|
|
757
|
+
);
|
|
758
|
+
if (options.expectedChainId !== void 0) {
|
|
759
|
+
const expected = assertChainId(options.expectedChainId);
|
|
760
|
+
if (expected !== chainId) {
|
|
761
|
+
throw typedDataError(
|
|
762
|
+
`\`domain.chainId\` is ${chainId}, but this client is pinned to ${expected}. Refusing to prompt: a signature for another chain is not the one you asked for.`,
|
|
763
|
+
"typedData.domain.chainId"
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (options.expectedAttestor !== void 0) {
|
|
768
|
+
const expected = assertAddress(options.expectedAttestor, "expectedAttestor");
|
|
769
|
+
if (expected !== verifyingContract) {
|
|
770
|
+
throw typedDataError(
|
|
771
|
+
`\`domain.verifyingContract\` is ${verifyingContract}, but this client is pinned to ${expected}. Refusing to prompt: the investor would be authorising a different \`DocumentAttestor\` deployment than the one this integration trusts.`,
|
|
772
|
+
"typedData.domain.verifyingContract"
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const message = asRecord2(record["message"]);
|
|
777
|
+
if (message === void 0) {
|
|
778
|
+
throw typedDataError("`message` is missing or is not an object.", "typedData.message");
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
domain: {
|
|
782
|
+
name: DOCUMENT_ATTESTOR_DOMAIN_NAME,
|
|
783
|
+
version: DOCUMENT_ATTESTOR_DOMAIN_VERSION,
|
|
784
|
+
chainId,
|
|
785
|
+
verifyingContract
|
|
786
|
+
},
|
|
787
|
+
types: DOCUMENT_ACCEPTANCE_TYPES,
|
|
788
|
+
primaryType: DOCUMENT_ACCEPTANCE_PRIMARY_TYPE,
|
|
789
|
+
message: {
|
|
790
|
+
nonce: assertNonce(message["nonce"]),
|
|
791
|
+
docHash: assertDocHash(message["docHash"]),
|
|
792
|
+
deadline: toDeadlineBigInt(message["deadline"])
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function toEip1193TypedData(typedData, options = {}) {
|
|
797
|
+
return toEip712JsonPayload(toViemTypedData(typedData, options));
|
|
798
|
+
}
|
|
799
|
+
function toEip1193Json(typedData, options = {}) {
|
|
800
|
+
return toEip712Json(toViemTypedData(typedData, options));
|
|
801
|
+
}
|
|
802
|
+
function assertAcceptanceFields(fields) {
|
|
803
|
+
const expected = DOCUMENT_ACCEPTANCE_TYPES.DocumentAcceptance;
|
|
804
|
+
if (!Array.isArray(fields)) {
|
|
805
|
+
throw typedDataError(
|
|
806
|
+
`\`types.${DOCUMENT_ACCEPTANCE_PRIMARY_TYPE}\` is missing or is not an array.`,
|
|
807
|
+
`typedData.types.${DOCUMENT_ACCEPTANCE_PRIMARY_TYPE}`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
if (fields.length !== expected.length) {
|
|
811
|
+
throw typedDataError(
|
|
812
|
+
`\`types.${DOCUMENT_ACCEPTANCE_PRIMARY_TYPE}\` must declare exactly ${expected.length} fields; received ${fields.length}. The field list is part of the type hash.`,
|
|
813
|
+
`typedData.types.${DOCUMENT_ACCEPTANCE_PRIMARY_TYPE}`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
for (const [index, want] of expected.entries()) {
|
|
817
|
+
const got = asRecord2(fields[index]);
|
|
818
|
+
if (got?.["name"] !== want.name || got?.["type"] !== want.type) {
|
|
819
|
+
throw typedDataError(
|
|
820
|
+
`\`types.${DOCUMENT_ACCEPTANCE_PRIMARY_TYPE}[${index}]\` must be \`${want.type} ${want.name}\`; received \`${describe(got?.["type"])} ${describe(got?.["name"])}\`. Names, types and ORDER are all part of the type hash, so a reordered list signs a different struct.`,
|
|
821
|
+
`typedData.types.${DOCUMENT_ACCEPTANCE_PRIMARY_TYPE}[${index}]`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
var EIP712_DOMAIN_TYPE_NAME = "EIP712Domain";
|
|
827
|
+
function assertDomainFieldsIfDeclared(fields) {
|
|
828
|
+
if (fields === void 0) return;
|
|
829
|
+
const detail = "Declaring it differently is not additive: it replaces the domain type the wallet would derive, which changes the domain hash and therefore the digest \u2014 a well-formed signature over the wrong message, caught only when the relay simulates the attestation.";
|
|
830
|
+
const matches = Array.isArray(fields) && fields.length === EIP712_DOMAIN_TYPE.length && EIP712_DOMAIN_TYPE.every((want, index) => {
|
|
831
|
+
const got = asRecord2(fields[index]);
|
|
832
|
+
return got?.["name"] === want.name && got?.["type"] === want.type;
|
|
833
|
+
});
|
|
834
|
+
if (!matches) {
|
|
835
|
+
const encoded = EIP712_DOMAIN_TYPE.map((field) => `${field.type} ${field.name}`).join(",");
|
|
836
|
+
throw typedDataError(
|
|
837
|
+
`\`types.${EIP712_DOMAIN_TYPE_NAME}\`, when present, must be exactly \`${encoded}\`; received ${describeFieldList(fields)}. ${detail}`,
|
|
838
|
+
`typedData.types.${EIP712_DOMAIN_TYPE_NAME}`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function describeFieldList(fields) {
|
|
843
|
+
if (!Array.isArray(fields)) return describe(fields);
|
|
844
|
+
if (fields.length === 0) return "an EMPTY field list";
|
|
845
|
+
return `\`${fields.map((field) => {
|
|
846
|
+
const got = asRecord2(field);
|
|
847
|
+
return `${String(got?.["type"])} ${String(got?.["name"])}`;
|
|
848
|
+
}).join(",")}\``;
|
|
849
|
+
}
|
|
850
|
+
function toDeadlineBigInt(value) {
|
|
851
|
+
if (typeof value === "bigint" && value >= 0n) return value;
|
|
852
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return BigInt(value);
|
|
853
|
+
if (typeof value === "string" && /^\d+$/.test(value.trim())) return BigInt(value.trim());
|
|
854
|
+
throw typedDataError(
|
|
855
|
+
`\`message.deadline\` must be a non-negative integer of unix seconds; received ${describe(value)}. The backend sends it as a DECIMAL STRING \u2014 that is the form \`eth_signTypedData_v4\` is specified in terms of \u2014 and this is where it is checked and normalised into the \`bigint\` the viem-shaped payload carries.`,
|
|
856
|
+
"typedData.message.deadline"
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
function typedDataError(detail, field) {
|
|
860
|
+
return new OpacityAttestationError(
|
|
861
|
+
"MalformedInput",
|
|
862
|
+
`The server's \`typedData\` cannot be trusted: ${detail} Refusing to hand it to a wallet.`,
|
|
863
|
+
{ field }
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
function describe(value) {
|
|
867
|
+
if (typeof value === "string") return `\`${value}\``;
|
|
868
|
+
if (value === void 0) return "nothing";
|
|
869
|
+
if (value === null) return "null";
|
|
870
|
+
if (typeof value === "object") return Array.isArray(value) ? "an array" : "an object";
|
|
871
|
+
return `\`${String(value)}\``;
|
|
872
|
+
}
|
|
873
|
+
function asRecord2(value) {
|
|
874
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
875
|
+
return value;
|
|
876
|
+
}
|
|
877
|
+
function asNumber(value) {
|
|
878
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
879
|
+
if (typeof value === "bigint") return Number(value);
|
|
880
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
881
|
+
const parsed = Number(value);
|
|
882
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
883
|
+
}
|
|
884
|
+
return void 0;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
export { SANDBOX_CHAIN_ID, SANDBOX_DEPLOYMENTS, SANDBOX_IMPLEMENTATION_VERSION, SANDBOX_TRAIT_IDS, SEPOLIA_CHAIN_ID, SEPOLIA_SANDBOX_MANIFEST, assertDocumentMatchesHash, buildAttestDocumentTransaction, buildDocumentAcceptanceTypedData, buildSandboxAddSelfTransaction, buildSandboxRegisterIssuerTransaction, buildSandboxSetSelfTraitTransaction, documentAttestorAbi, documentTraitId, getSandboxDeployment, hashDocument, hashDocumentAcceptance, probeSandboxRegistry, requireSandboxDeployment, sandboxAllowlistRegistryAbi, sandboxUserId, toEip1193Json, toEip1193TypedData, toEip712Json, toEip712JsonPayload, toEthersTypedData, toJsonRpcTransaction, toSandboxAddSelfWriteContractRequest, toSandboxRegisterIssuerWriteContractRequest, toSandboxSetSelfTraitWriteContractRequest, toViemTypedData, toWriteContractRequest, verifyAttestationSession };
|
|
888
|
+
//# sourceMappingURL=chunk-7ODYBEZH.js.map
|
|
889
|
+
//# sourceMappingURL=chunk-7ODYBEZH.js.map
|