@epoch-protocol/epoch-commons-sdk 0.1.17 → 0.1.18
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/utils/compact.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Hex } from "viem";
|
|
2
|
-
import { CompactData } from "../types/index.js";
|
|
2
|
+
import { CompactData, Mandate } from "../types/index.js";
|
|
3
3
|
export declare function getCompactTypehash(witnessTypeString?: string): Hex;
|
|
4
|
+
export declare function getSimpleWitnessHash(mandate: Mandate, witnessTypeString: string): Hex;
|
|
4
5
|
export declare function getClaimHash(message: CompactData, witnessTypeString: string): `0x${string}`;
|
package/dist/utils/compact.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { keccak256, toBytes
|
|
1
|
+
import { keccak256, toBytes } from "viem";
|
|
2
2
|
import { hashStruct } from "viem/utils";
|
|
3
3
|
export function getCompactTypehash(witnessTypeString) {
|
|
4
4
|
if (witnessTypeString) {
|
|
@@ -8,14 +8,76 @@ export function getCompactTypehash(witnessTypeString) {
|
|
|
8
8
|
const typestring = "Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,bytes12 lockTag,address token,uint256 amount)";
|
|
9
9
|
return keccak256(toBytes(typestring));
|
|
10
10
|
}
|
|
11
|
+
function parseWitnessFields(witnessTypeString) {
|
|
12
|
+
return witnessTypeString.split(",").map((field) => {
|
|
13
|
+
const tokens = field.trim().split(/\s+/).filter(Boolean);
|
|
14
|
+
if (tokens.length < 2) {
|
|
15
|
+
throw new Error(`Invalid witness field "${field}" (expected "type name" in witnessTypeString)`);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
type: tokens[0],
|
|
19
|
+
name: tokens[tokens.length - 1],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function normalizeWitnessMandate(mandate, witnessTypeString) {
|
|
24
|
+
if (!mandate)
|
|
25
|
+
return mandate;
|
|
26
|
+
const fields = parseWitnessFields(witnessTypeString);
|
|
27
|
+
const normalized = { ...mandate };
|
|
28
|
+
for (const { type, name } of fields) {
|
|
29
|
+
const value = normalized[name];
|
|
30
|
+
if (value === undefined || value === null)
|
|
31
|
+
continue;
|
|
32
|
+
if (type === "uint256") {
|
|
33
|
+
normalized[name] = BigInt(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
function mandateWitnessValuesInOrder(mandate, witnessTypeString) {
|
|
39
|
+
const fields = witnessTypeString
|
|
40
|
+
.split(",")
|
|
41
|
+
.map((f) => f.trim())
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
return fields.map((field) => {
|
|
44
|
+
const tokens = field.split(/\s+/).filter(Boolean);
|
|
45
|
+
if (tokens.length < 2) {
|
|
46
|
+
throw new Error(`Invalid witness field "${field}" (expected "type name" in witnessTypeString)`);
|
|
47
|
+
}
|
|
48
|
+
const name = tokens[tokens.length - 1];
|
|
49
|
+
const record = mandate;
|
|
50
|
+
if (!(name in record) ||
|
|
51
|
+
record[name] === undefined ||
|
|
52
|
+
record[name] === null) {
|
|
53
|
+
throw new Error(`Mandate missing field "${name}" required by witnessTypeString (witness hash)`);
|
|
54
|
+
}
|
|
55
|
+
return record[name];
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function getSimpleWitnessHash(mandate, witnessTypeString) {
|
|
59
|
+
const normalizedMandate = normalizeWitnessMandate(mandate, witnessTypeString);
|
|
60
|
+
const mandateTypes = parseWitnessFields(witnessTypeString);
|
|
61
|
+
const witnessData = mandateWitnessValuesInOrder(normalizedMandate, witnessTypeString).reduce((acc, value, index) => {
|
|
62
|
+
acc[mandateTypes[index].name] = value;
|
|
63
|
+
return acc;
|
|
64
|
+
}, {});
|
|
65
|
+
return hashStruct({
|
|
66
|
+
types: { Mandate: mandateTypes },
|
|
67
|
+
primaryType: "Mandate",
|
|
68
|
+
data: witnessData,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
11
71
|
export function getClaimHash(message, witnessTypeString) {
|
|
12
|
-
|
|
72
|
+
const mandate = normalizeWitnessMandate(message.mandate, witnessTypeString);
|
|
13
73
|
const messageWithFormattedData = {
|
|
14
74
|
...message,
|
|
75
|
+
amount: BigInt(message.amount),
|
|
15
76
|
lockTag: `0x${message.lockTag.toString(16).padStart(24, "0")}`,
|
|
16
77
|
token: typeof message.token === "bigint"
|
|
17
78
|
? `0x${message.token.toString(16).padStart(40, "0")}`
|
|
18
79
|
: message.token,
|
|
80
|
+
...(mandate ? { mandate } : {}),
|
|
19
81
|
};
|
|
20
82
|
return hashStruct({
|
|
21
83
|
types: getTypes(messageWithFormattedData, witnessTypeString),
|
|
@@ -37,10 +99,7 @@ function getTypes(message, witnessTypeString) {
|
|
|
37
99
|
],
|
|
38
100
|
...(message.mandate
|
|
39
101
|
? {
|
|
40
|
-
Mandate: witnessTypeString
|
|
41
|
-
const [type, name] = field.trim().split(/\s+/);
|
|
42
|
-
return { name, type };
|
|
43
|
-
}),
|
|
102
|
+
Mandate: parseWitnessFields(witnessTypeString),
|
|
44
103
|
}
|
|
45
104
|
: {}),
|
|
46
105
|
};
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Base Mandate fields shared by all Epoch intents (matches `getTaskData`). */
|
|
2
|
+
export declare const BASE_MANDATE_TYPESTRING = "address tokenIn,uint256 tokenInAmount,address tokenOut,uint256 minTokenOut,uint256 destinationChainId,bytes4 taskType,bytes32 protocolHashIdentifier,address recipient";
|
|
3
|
+
/** Miden → EVM cross-chain bridge witness suffix (canonical). */
|
|
4
|
+
export declare const MIDEN_TO_EVM_EXTRA_TYPESTRING = "string midenSourceAccount,string midenFaucetId,string midenNoteType,string midenNoteId";
|
|
5
|
+
/** EVM → Miden cross-chain bridge witness suffix (canonical). */
|
|
6
|
+
export declare const EVM_TO_MIDEN_EXTRA_TYPESTRING = "string midenRecipientAccount,string midenFaucetId";
|
|
7
|
+
export declare const MIDEN_TO_EVM_WITNESS_TYPESTRING = "address tokenIn,uint256 tokenInAmount,address tokenOut,uint256 minTokenOut,uint256 destinationChainId,bytes4 taskType,bytes32 protocolHashIdentifier,address recipient,string midenSourceAccount,string midenFaucetId,string midenNoteType,string midenNoteId";
|
|
8
|
+
export declare const EVM_TO_MIDEN_WITNESS_TYPESTRING = "address tokenIn,uint256 tokenInAmount,address tokenOut,uint256 minTokenOut,uint256 destinationChainId,bytes4 taskType,bytes32 protocolHashIdentifier,address recipient,string midenRecipientAccount,string midenFaucetId";
|
|
9
|
+
export type WitnessTypestringValidationResult = {
|
|
10
|
+
valid: true;
|
|
11
|
+
} | {
|
|
12
|
+
valid: false;
|
|
13
|
+
error: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function parseWitnessTypestringFields(witnessTypeString: string): Array<{
|
|
16
|
+
type: string;
|
|
17
|
+
name: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* When a mandate includes Miden bridge metadata, the witness typestring must end
|
|
21
|
+
* with the canonical Miden suffix. Direction (Miden→EVM vs EVM→Miden) is inferred
|
|
22
|
+
* from the mandate and from the typestring's own suffix.
|
|
23
|
+
*
|
|
24
|
+
* No-op for pure EVM intents — Miden checks run only when {@link isMidenIntent}.
|
|
25
|
+
*/
|
|
26
|
+
export declare function assertMidenWitnessSuffix(witnessTypeString: string, mandate?: Record<string, unknown>): WitnessTypestringValidationResult;
|
|
27
|
+
export declare function isEVMToMidenMandate(mandate?: Record<string, unknown>): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Validates a witness typestring against its data. Handles both the full witness
|
|
30
|
+
* (base mandate + extra suffix) and a bare extra suffix — the same rules apply to
|
|
31
|
+
* either: comma spacing, "type name" format, key alignment with the data object,
|
|
32
|
+
* and the canonical Miden bridge suffix. Pass the assembled typestring and the
|
|
33
|
+
* merged data; there is no separate extra-suffix validator.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateWitnessTypeString(witnessTypeString: string, mandate?: Record<string, unknown>): WitnessTypestringValidationResult;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/** Base Mandate fields shared by all Epoch intents (matches `getTaskData`). */
|
|
2
|
+
export const BASE_MANDATE_TYPESTRING = "address tokenIn,uint256 tokenInAmount,address tokenOut,uint256 minTokenOut,uint256 destinationChainId,bytes4 taskType,bytes32 protocolHashIdentifier,address recipient";
|
|
3
|
+
/** Miden → EVM cross-chain bridge witness suffix (canonical). */
|
|
4
|
+
export const MIDEN_TO_EVM_EXTRA_TYPESTRING = "string midenSourceAccount,string midenFaucetId,string midenNoteType,string midenNoteId";
|
|
5
|
+
/** EVM → Miden cross-chain bridge witness suffix (canonical). */
|
|
6
|
+
export const EVM_TO_MIDEN_EXTRA_TYPESTRING = "string midenRecipientAccount,string midenFaucetId";
|
|
7
|
+
export const MIDEN_TO_EVM_WITNESS_TYPESTRING = `${BASE_MANDATE_TYPESTRING},${MIDEN_TO_EVM_EXTRA_TYPESTRING}`;
|
|
8
|
+
export const EVM_TO_MIDEN_WITNESS_TYPESTRING = `${BASE_MANDATE_TYPESTRING},${EVM_TO_MIDEN_EXTRA_TYPESTRING}`;
|
|
9
|
+
export function parseWitnessTypestringFields(witnessTypeString) {
|
|
10
|
+
return witnessTypeString
|
|
11
|
+
.split(",")
|
|
12
|
+
.map((field) => field.trim())
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.map((field) => {
|
|
15
|
+
const tokens = field.split(/\s+/).filter(Boolean);
|
|
16
|
+
if (tokens.length < 2) {
|
|
17
|
+
throw new Error(`Invalid witness field "${field}" (expected "type name" in witnessTypeString)`);
|
|
18
|
+
}
|
|
19
|
+
const name = tokens[tokens.length - 1];
|
|
20
|
+
const type = tokens.slice(0, -1).join(" ");
|
|
21
|
+
return { type, name };
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function assertNoSpacesAroundCommas(typestring) {
|
|
25
|
+
if (/,(\s)|(\s),/.test(typestring)) {
|
|
26
|
+
return {
|
|
27
|
+
valid: false,
|
|
28
|
+
error: 'Typestring must not contain spaces before or after commas (e.g. use "uint256 foo,uint256 bar", not "uint256 foo, uint256 bar")',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { valid: true };
|
|
32
|
+
}
|
|
33
|
+
function assertKeysMatchTypestring(typestring, data, dataLabel = "extraData", typestringLabel = "extraDataTypestring") {
|
|
34
|
+
let fields;
|
|
35
|
+
try {
|
|
36
|
+
fields = parseWitnessTypestringFields(typestring);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
valid: false,
|
|
41
|
+
error: error instanceof Error
|
|
42
|
+
? error.message
|
|
43
|
+
: "Invalid witnessTypeString format",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const typeNames = new Set(fields.map((f) => f.name));
|
|
47
|
+
const dataKeys = new Set(Object.keys(data));
|
|
48
|
+
for (const name of typeNames) {
|
|
49
|
+
if (!dataKeys.has(name)) {
|
|
50
|
+
return {
|
|
51
|
+
valid: false,
|
|
52
|
+
error: `${dataLabel} is missing key "${name}" required by ${typestringLabel}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const key of dataKeys) {
|
|
57
|
+
if (!typeNames.has(key)) {
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
error: `${dataLabel} has unexpected key "${key}" not declared in ${typestringLabel}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { valid: true };
|
|
65
|
+
}
|
|
66
|
+
function isEvmToMidenMandate(mandate) {
|
|
67
|
+
return (mandate?.midenRecipientAccount != null &&
|
|
68
|
+
mandate?.midenSourceAccount == null);
|
|
69
|
+
}
|
|
70
|
+
function isMidenToEvmMandate(mandate) {
|
|
71
|
+
return mandate?.midenSourceAccount != null;
|
|
72
|
+
}
|
|
73
|
+
function hasMidenToEvmSuffix(witnessTypeString) {
|
|
74
|
+
return witnessTypeString.includes("midenSourceAccount");
|
|
75
|
+
}
|
|
76
|
+
function hasEvmToMidenSuffix(witnessTypeString) {
|
|
77
|
+
return (witnessTypeString.includes("midenRecipientAccount") &&
|
|
78
|
+
!witnessTypeString.includes("midenSourceAccount"));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* True iff the intent is a Miden bridge intent — i.e. it carries Miden markers
|
|
82
|
+
* either in the mandate (midenSourceAccount / midenRecipientAccount) or in the
|
|
83
|
+
* witness typestring itself. Pure EVM intents carry none of these, so all Miden
|
|
84
|
+
* validation is skipped for them.
|
|
85
|
+
*/
|
|
86
|
+
function isMidenIntent(witnessTypeString, mandate) {
|
|
87
|
+
return (isMidenToEvmMandate(mandate) ||
|
|
88
|
+
isEvmToMidenMandate(mandate) ||
|
|
89
|
+
hasMidenToEvmSuffix(witnessTypeString) ||
|
|
90
|
+
hasEvmToMidenSuffix(witnessTypeString));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* When a mandate includes Miden bridge metadata, the witness typestring must end
|
|
94
|
+
* with the canonical Miden suffix. Direction (Miden→EVM vs EVM→Miden) is inferred
|
|
95
|
+
* from the mandate and from the typestring's own suffix.
|
|
96
|
+
*
|
|
97
|
+
* No-op for pure EVM intents — Miden checks run only when {@link isMidenIntent}.
|
|
98
|
+
*/
|
|
99
|
+
export function assertMidenWitnessSuffix(witnessTypeString, mandate) {
|
|
100
|
+
if (!isMidenIntent(witnessTypeString, mandate)) {
|
|
101
|
+
return { valid: true };
|
|
102
|
+
}
|
|
103
|
+
const toEvm = isMidenToEvmMandate(mandate) || hasMidenToEvmSuffix(witnessTypeString);
|
|
104
|
+
const fromEvm = isEvmToMidenMandate(mandate) || hasEvmToMidenSuffix(witnessTypeString);
|
|
105
|
+
if (toEvm && fromEvm) {
|
|
106
|
+
return {
|
|
107
|
+
valid: false,
|
|
108
|
+
error: "Miden witness cannot include both midenSourceAccount and midenRecipientAccount",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const expectedSuffix = toEvm
|
|
112
|
+
? MIDEN_TO_EVM_EXTRA_TYPESTRING
|
|
113
|
+
: EVM_TO_MIDEN_EXTRA_TYPESTRING;
|
|
114
|
+
if (!witnessTypeString.endsWith(expectedSuffix)) {
|
|
115
|
+
return {
|
|
116
|
+
valid: false,
|
|
117
|
+
error: `Invalid Miden witness suffix. Expected typestring to end with "${expectedSuffix}".`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { valid: true };
|
|
121
|
+
}
|
|
122
|
+
export function isEVMToMidenMandate(mandate) {
|
|
123
|
+
return isEvmToMidenMandate(mandate);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Validates a witness typestring against its data. Handles both the full witness
|
|
127
|
+
* (base mandate + extra suffix) and a bare extra suffix — the same rules apply to
|
|
128
|
+
* either: comma spacing, "type name" format, key alignment with the data object,
|
|
129
|
+
* and the canonical Miden bridge suffix. Pass the assembled typestring and the
|
|
130
|
+
* merged data; there is no separate extra-suffix validator.
|
|
131
|
+
*/
|
|
132
|
+
export function validateWitnessTypeString(witnessTypeString, mandate) {
|
|
133
|
+
if (mandate?.midenSourceAccount != null &&
|
|
134
|
+
mandate?.midenRecipientAccount != null) {
|
|
135
|
+
return {
|
|
136
|
+
valid: false,
|
|
137
|
+
error: "Miden witness cannot include both midenSourceAccount and midenRecipientAccount",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (!witnessTypeString || witnessTypeString.trim() === "") {
|
|
141
|
+
return { valid: true };
|
|
142
|
+
}
|
|
143
|
+
const commaCheck = assertNoSpacesAroundCommas(witnessTypeString);
|
|
144
|
+
if (!commaCheck.valid)
|
|
145
|
+
return commaCheck;
|
|
146
|
+
try {
|
|
147
|
+
parseWitnessTypestringFields(witnessTypeString);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
valid: false,
|
|
152
|
+
error: error instanceof Error
|
|
153
|
+
? error.message
|
|
154
|
+
: "Invalid witnessTypeString format",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (mandate) {
|
|
158
|
+
const keyCheck = assertKeysMatchTypestring(witnessTypeString, mandate, "mandate", "witnessTypeString");
|
|
159
|
+
if (!keyCheck.valid)
|
|
160
|
+
return keyCheck;
|
|
161
|
+
}
|
|
162
|
+
const midenCheck = assertMidenWitnessSuffix(witnessTypeString, mandate);
|
|
163
|
+
if (!midenCheck.valid)
|
|
164
|
+
return midenCheck;
|
|
165
|
+
return { valid: true };
|
|
166
|
+
}
|