@epoch-protocol/epoch-commons-sdk 0.1.17 → 0.1.19
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 +2 -1
- package/dist/utils/compact.js +65 -6
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/miden-intent.d.ts +29 -0
- package/dist/utils/miden-intent.js +79 -0
- package/dist/utils/witness-typestring-constants.d.ts +18 -0
- package/dist/utils/witness-typestring-constants.js +31 -0
- package/dist/utils/witness-typestring-parse.d.ts +5 -0
- package/dist/utils/witness-typestring-parse.js +18 -0
- package/dist/utils/witness-typestring.d.ts +53 -0
- package/dist/utils/witness-typestring.js +237 -0
- package/package.json +1 -1
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,29 @@
|
|
|
1
|
+
import { EVM_TO_MIDEN_EXTRA_TYPESTRING, EVM_TO_MIDEN_FIELD_NAMES, MIDEN_MARKER_FIELD_NAMES, MIDEN_TO_EVM_EXTRA_TYPESTRING, MIDEN_TO_EVM_FIELD_NAMES } from "./witness-typestring.js";
|
|
2
|
+
export type MidenBridgeDirection = "miden-to-evm" | "evm-to-miden";
|
|
3
|
+
export interface MidenExtraData {
|
|
4
|
+
midenSourceAccount?: string;
|
|
5
|
+
midenRecipientAccount?: string;
|
|
6
|
+
midenFaucetId?: string;
|
|
7
|
+
midenNoteType?: string;
|
|
8
|
+
midenNoteId?: string;
|
|
9
|
+
}
|
|
10
|
+
/** True when mandate/extra data carries any Miden bridge marker field. */
|
|
11
|
+
export declare function hasMidenMarkersInData(data?: Record<string, unknown>): boolean;
|
|
12
|
+
/** True when a witness/extra typestring declares any Miden bridge marker field. */
|
|
13
|
+
export declare function hasMidenMarkersInTypestring(typestring: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* True iff the intent is a Miden bridge intent (markers in typestring and/or data).
|
|
16
|
+
* At least one of `typestring` or `data` must be provided.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isMidenIntent(typestring?: string, data?: Record<string, unknown>): boolean;
|
|
19
|
+
/** EVM→Miden: recipient set, source account absent. */
|
|
20
|
+
export declare function isEVMToMidenIntent(data?: Record<string, unknown>): boolean;
|
|
21
|
+
/** Miden→EVM: source account set. */
|
|
22
|
+
export declare function isMidenToEvmIntent(data?: Record<string, unknown>): boolean;
|
|
23
|
+
export declare function hasMidenToEvmTypestringMarker(typestring: string): boolean;
|
|
24
|
+
export declare function hasEvmToMidenTypestringMarker(typestring: string): boolean;
|
|
25
|
+
export declare function resolveMidenBridgeDirection(typestring: string, data?: Record<string, unknown>): MidenBridgeDirection | "conflict" | null;
|
|
26
|
+
export declare function getMidenMetadata(data?: Record<string, unknown>, typestring?: string): MidenExtraData | null;
|
|
27
|
+
/** @deprecated Use {@link isEVMToMidenIntent}. */
|
|
28
|
+
export declare function isEVMToMidenMandate(mandate?: Record<string, unknown>): boolean;
|
|
29
|
+
export { EVM_TO_MIDEN_EXTRA_TYPESTRING, EVM_TO_MIDEN_FIELD_NAMES, MIDEN_TO_EVM_EXTRA_TYPESTRING, MIDEN_TO_EVM_FIELD_NAMES, MIDEN_MARKER_FIELD_NAMES, };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { EVM_TO_MIDEN_EXTRA_TYPESTRING, EVM_TO_MIDEN_FIELD_NAMES, MIDEN_MARKER_FIELD_NAMES, MIDEN_RECIPIENT_ACCOUNT_FIELD_NAME, MIDEN_SOURCE_ACCOUNT_FIELD_NAME, MIDEN_TO_EVM_EXTRA_TYPESTRING, MIDEN_TO_EVM_FIELD_NAMES, } from "./witness-typestring.js";
|
|
2
|
+
function hasTruthyField(data, fieldName) {
|
|
3
|
+
return data[fieldName] != null && data[fieldName] !== "";
|
|
4
|
+
}
|
|
5
|
+
/** True when mandate/extra data carries any Miden bridge marker field. */
|
|
6
|
+
export function hasMidenMarkersInData(data) {
|
|
7
|
+
if (!data)
|
|
8
|
+
return false;
|
|
9
|
+
return MIDEN_MARKER_FIELD_NAMES.some((name) => hasTruthyField(data, name));
|
|
10
|
+
}
|
|
11
|
+
/** True when a witness/extra typestring declares any Miden bridge marker field. */
|
|
12
|
+
export function hasMidenMarkersInTypestring(typestring) {
|
|
13
|
+
if (!typestring.trim())
|
|
14
|
+
return false;
|
|
15
|
+
return MIDEN_MARKER_FIELD_NAMES.some((name) => typestring.includes(name));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* True iff the intent is a Miden bridge intent (markers in typestring and/or data).
|
|
19
|
+
* At least one of `typestring` or `data` must be provided.
|
|
20
|
+
*/
|
|
21
|
+
export function isMidenIntent(typestring, data) {
|
|
22
|
+
const hasTypestring = typestring !== undefined && typestring.trim() !== "";
|
|
23
|
+
const hasData = data !== undefined;
|
|
24
|
+
if (!hasTypestring && !hasData) {
|
|
25
|
+
throw new Error("isMidenIntent requires typestring and/or data");
|
|
26
|
+
}
|
|
27
|
+
return ((hasTypestring && hasMidenMarkersInTypestring(typestring)) ||
|
|
28
|
+
hasMidenMarkersInData(data));
|
|
29
|
+
}
|
|
30
|
+
/** EVM→Miden: recipient set, source account absent. */
|
|
31
|
+
export function isEVMToMidenIntent(data) {
|
|
32
|
+
if (!data)
|
|
33
|
+
return false;
|
|
34
|
+
return (hasTruthyField(data, MIDEN_RECIPIENT_ACCOUNT_FIELD_NAME) &&
|
|
35
|
+
!hasTruthyField(data, MIDEN_SOURCE_ACCOUNT_FIELD_NAME));
|
|
36
|
+
}
|
|
37
|
+
/** Miden→EVM: source account set. */
|
|
38
|
+
export function isMidenToEvmIntent(data) {
|
|
39
|
+
if (!data)
|
|
40
|
+
return false;
|
|
41
|
+
return hasTruthyField(data, MIDEN_SOURCE_ACCOUNT_FIELD_NAME);
|
|
42
|
+
}
|
|
43
|
+
export function hasMidenToEvmTypestringMarker(typestring) {
|
|
44
|
+
return typestring.includes(MIDEN_SOURCE_ACCOUNT_FIELD_NAME);
|
|
45
|
+
}
|
|
46
|
+
export function hasEvmToMidenTypestringMarker(typestring) {
|
|
47
|
+
return typestring.includes(MIDEN_RECIPIENT_ACCOUNT_FIELD_NAME);
|
|
48
|
+
}
|
|
49
|
+
export function resolveMidenBridgeDirection(typestring, data) {
|
|
50
|
+
const toEvm = isMidenToEvmIntent(data) || hasMidenToEvmTypestringMarker(typestring);
|
|
51
|
+
const fromEvm = isEVMToMidenIntent(data) || hasEvmToMidenTypestringMarker(typestring);
|
|
52
|
+
if (toEvm && fromEvm)
|
|
53
|
+
return "conflict";
|
|
54
|
+
if (toEvm)
|
|
55
|
+
return "miden-to-evm";
|
|
56
|
+
if (fromEvm)
|
|
57
|
+
return "evm-to-miden";
|
|
58
|
+
// Match server routing: other Miden markers without a recipient use collateral path.
|
|
59
|
+
if (isMidenIntent(typestring, data))
|
|
60
|
+
return "miden-to-evm";
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
export function getMidenMetadata(data, typestring) {
|
|
64
|
+
if (!data || !isMidenIntent(typestring, data))
|
|
65
|
+
return null;
|
|
66
|
+
return {
|
|
67
|
+
midenSourceAccount: data?.midenSourceAccount,
|
|
68
|
+
midenRecipientAccount: data?.midenRecipientAccount,
|
|
69
|
+
midenFaucetId: data?.midenFaucetId,
|
|
70
|
+
midenNoteType: data?.midenNoteType,
|
|
71
|
+
midenNoteId: data?.midenNoteId,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** @deprecated Use {@link isEVMToMidenIntent}. */
|
|
75
|
+
export function isEVMToMidenMandate(mandate) {
|
|
76
|
+
return isEVMToMidenIntent(mandate);
|
|
77
|
+
}
|
|
78
|
+
// Re-export canonical typestrings for callers that import direction helpers here.
|
|
79
|
+
export { EVM_TO_MIDEN_EXTRA_TYPESTRING, EVM_TO_MIDEN_FIELD_NAMES, MIDEN_TO_EVM_EXTRA_TYPESTRING, MIDEN_TO_EVM_FIELD_NAMES, MIDEN_MARKER_FIELD_NAMES, };
|
|
@@ -0,0 +1,18 @@
|
|
|
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 declare const MIDEN_TO_EVM_FIELD_NAMES: string[];
|
|
10
|
+
export declare const EVM_TO_MIDEN_FIELD_NAMES: string[];
|
|
11
|
+
/** Union of all Miden bridge field names from the canonical extra typestrings. */
|
|
12
|
+
export declare const MIDEN_MARKER_FIELD_NAMES: string[];
|
|
13
|
+
/** Fields declared only on the Miden→EVM extra typestring. */
|
|
14
|
+
export declare const MIDEN_TO_EVM_ONLY_FIELD_NAMES: string[];
|
|
15
|
+
/** Fields declared only on the EVM→Miden extra typestring. */
|
|
16
|
+
export declare const EVM_TO_MIDEN_ONLY_FIELD_NAMES: string[];
|
|
17
|
+
export declare const MIDEN_SOURCE_ACCOUNT_FIELD_NAME: "midenSourceAccount";
|
|
18
|
+
export declare const MIDEN_RECIPIENT_ACCOUNT_FIELD_NAME: "midenRecipientAccount";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { fieldNamesFromExtraTypestring } from "./witness-typestring-parse.js";
|
|
2
|
+
function uniqueFieldNames(typestrings) {
|
|
3
|
+
return [
|
|
4
|
+
...new Set(typestrings.flatMap((typestring) => fieldNamesFromExtraTypestring(typestring))),
|
|
5
|
+
];
|
|
6
|
+
}
|
|
7
|
+
function onlyInLeft(left, right) {
|
|
8
|
+
const rightSet = new Set(right);
|
|
9
|
+
return left.filter((name) => !rightSet.has(name));
|
|
10
|
+
}
|
|
11
|
+
/** Base Mandate fields shared by all Epoch intents (matches `getTaskData`). */
|
|
12
|
+
export const BASE_MANDATE_TYPESTRING = "address tokenIn,uint256 tokenInAmount,address tokenOut,uint256 minTokenOut,uint256 destinationChainId,bytes4 taskType,bytes32 protocolHashIdentifier,address recipient";
|
|
13
|
+
/** Miden → EVM cross-chain bridge witness suffix (canonical). */
|
|
14
|
+
export const MIDEN_TO_EVM_EXTRA_TYPESTRING = "string midenSourceAccount,string midenFaucetId,string midenNoteType,string midenNoteId";
|
|
15
|
+
/** EVM → Miden cross-chain bridge witness suffix (canonical). */
|
|
16
|
+
export const EVM_TO_MIDEN_EXTRA_TYPESTRING = "string midenRecipientAccount,string midenFaucetId";
|
|
17
|
+
export const MIDEN_TO_EVM_WITNESS_TYPESTRING = `${BASE_MANDATE_TYPESTRING},${MIDEN_TO_EVM_EXTRA_TYPESTRING}`;
|
|
18
|
+
export const EVM_TO_MIDEN_WITNESS_TYPESTRING = `${BASE_MANDATE_TYPESTRING},${EVM_TO_MIDEN_EXTRA_TYPESTRING}`;
|
|
19
|
+
export const MIDEN_TO_EVM_FIELD_NAMES = fieldNamesFromExtraTypestring(MIDEN_TO_EVM_EXTRA_TYPESTRING);
|
|
20
|
+
export const EVM_TO_MIDEN_FIELD_NAMES = fieldNamesFromExtraTypestring(EVM_TO_MIDEN_EXTRA_TYPESTRING);
|
|
21
|
+
/** Union of all Miden bridge field names from the canonical extra typestrings. */
|
|
22
|
+
export const MIDEN_MARKER_FIELD_NAMES = uniqueFieldNames([
|
|
23
|
+
MIDEN_TO_EVM_EXTRA_TYPESTRING,
|
|
24
|
+
EVM_TO_MIDEN_EXTRA_TYPESTRING,
|
|
25
|
+
]);
|
|
26
|
+
/** Fields declared only on the Miden→EVM extra typestring. */
|
|
27
|
+
export const MIDEN_TO_EVM_ONLY_FIELD_NAMES = onlyInLeft(MIDEN_TO_EVM_FIELD_NAMES, EVM_TO_MIDEN_FIELD_NAMES);
|
|
28
|
+
/** Fields declared only on the EVM→Miden extra typestring. */
|
|
29
|
+
export const EVM_TO_MIDEN_ONLY_FIELD_NAMES = onlyInLeft(EVM_TO_MIDEN_FIELD_NAMES, MIDEN_TO_EVM_FIELD_NAMES);
|
|
30
|
+
export const MIDEN_SOURCE_ACCOUNT_FIELD_NAME = MIDEN_TO_EVM_ONLY_FIELD_NAMES.find((name) => name === "midenSourceAccount");
|
|
31
|
+
export const MIDEN_RECIPIENT_ACCOUNT_FIELD_NAME = EVM_TO_MIDEN_ONLY_FIELD_NAMES.find((name) => name === "midenRecipientAccount");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function parseWitnessTypestringFields(witnessTypeString) {
|
|
2
|
+
return witnessTypeString
|
|
3
|
+
.split(",")
|
|
4
|
+
.map((field) => field.trim())
|
|
5
|
+
.filter(Boolean)
|
|
6
|
+
.map((field) => {
|
|
7
|
+
const tokens = field.split(/\s+/).filter(Boolean);
|
|
8
|
+
if (tokens.length < 2) {
|
|
9
|
+
throw new Error(`Invalid witness field "${field}" (expected "type name" in witnessTypeString)`);
|
|
10
|
+
}
|
|
11
|
+
const name = tokens[tokens.length - 1];
|
|
12
|
+
const type = tokens.slice(0, -1).join(" ");
|
|
13
|
+
return { type, name };
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function fieldNamesFromExtraTypestring(typestring) {
|
|
17
|
+
return parseWitnessTypestringFields(typestring).map((field) => field.name);
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export declare function parseWitnessTypestringFields(witnessTypeString: string): Array<{
|
|
2
|
+
type: string;
|
|
3
|
+
name: string;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function fieldNamesFromExtraTypestring(typestring: string): string[];
|
|
6
|
+
/** Base Mandate fields shared by all Epoch intents (matches `getTaskData`). */
|
|
7
|
+
export declare const BASE_MANDATE_TYPESTRING = "address tokenIn,uint256 tokenInAmount,address tokenOut,uint256 minTokenOut,uint256 destinationChainId,bytes4 taskType,bytes32 protocolHashIdentifier,address recipient";
|
|
8
|
+
/** Miden → EVM cross-chain bridge witness suffix (canonical). */
|
|
9
|
+
export declare const MIDEN_TO_EVM_EXTRA_TYPESTRING = "string midenSourceAccount,string midenFaucetId,string midenNoteType,string midenNoteId";
|
|
10
|
+
/** EVM → Miden cross-chain bridge witness suffix (canonical). */
|
|
11
|
+
export declare const EVM_TO_MIDEN_EXTRA_TYPESTRING = "string midenRecipientAccount,string midenFaucetId";
|
|
12
|
+
/** Lending DEPOSIT extraData suffix (canonical). */
|
|
13
|
+
export declare const DEPOSIT_EXTRADATA_TYPESTRING = "string marketUid,string action,string payAsset,bool isAll";
|
|
14
|
+
/** Lending WITHDRAW extraData suffix (canonical). */
|
|
15
|
+
export declare const WITHDRAW_EXTRADATA_TYPESTRING = "string marketUid,string action,string payAsset,bool isAll";
|
|
16
|
+
export declare const MIDEN_TO_EVM_FIELD_NAMES: string[];
|
|
17
|
+
export declare const EVM_TO_MIDEN_FIELD_NAMES: string[];
|
|
18
|
+
/** Union of all Miden bridge field names from the canonical extra typestrings. */
|
|
19
|
+
export declare const MIDEN_MARKER_FIELD_NAMES: string[];
|
|
20
|
+
/** Fields declared only on the Miden→EVM extra typestring. */
|
|
21
|
+
export declare const MIDEN_TO_EVM_ONLY_FIELD_NAMES: string[];
|
|
22
|
+
/** Fields declared only on the EVM→Miden extra typestring. */
|
|
23
|
+
export declare const EVM_TO_MIDEN_ONLY_FIELD_NAMES: string[];
|
|
24
|
+
export declare const MIDEN_SOURCE_ACCOUNT_FIELD_NAME: "midenSourceAccount";
|
|
25
|
+
export declare const MIDEN_RECIPIENT_ACCOUNT_FIELD_NAME: "midenRecipientAccount";
|
|
26
|
+
export type WitnessTypestringValidationResult = {
|
|
27
|
+
valid: true;
|
|
28
|
+
} | {
|
|
29
|
+
valid: false;
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Validate Miden `extraDataTypestring` / `extraData` for task assembly.
|
|
34
|
+
* Required Miden fields must be declared and present; additional protocol fields
|
|
35
|
+
* are allowed when declared in the typestring.
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateMidenExtraFields(extraDataTypestring: string, extraData?: Record<string, unknown>): WitnessTypestringValidationResult;
|
|
38
|
+
/**
|
|
39
|
+
* When a mandate includes Miden bridge metadata, the witness typestring must
|
|
40
|
+
* declare every required Miden field for the detected direction. Additional
|
|
41
|
+
* non-Miden fields may appear before or after the Miden fields.
|
|
42
|
+
*
|
|
43
|
+
* No-op for pure EVM intents — Miden checks run only when {@link isMidenIntent}.
|
|
44
|
+
*/
|
|
45
|
+
export declare function assertMidenWitnessFields(witnessTypeString: string, mandate?: Record<string, unknown>): WitnessTypestringValidationResult;
|
|
46
|
+
/**
|
|
47
|
+
* Validates a witness typestring against its data. Handles both the full witness
|
|
48
|
+
* (base mandate + extra suffix) and a bare extra suffix — the same rules apply to
|
|
49
|
+
* either: comma spacing, "type name" format, key alignment with the data object,
|
|
50
|
+
* and required Miden bridge fields when applicable. Pass the assembled typestring and the
|
|
51
|
+
* merged data; there is no separate extra-suffix validator.
|
|
52
|
+
*/
|
|
53
|
+
export declare function validateWitnessTypeString(witnessTypeString: string, mandate?: Record<string, unknown>): WitnessTypestringValidationResult;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { isMidenIntent, resolveMidenBridgeDirection } from "./miden-intent.js";
|
|
2
|
+
export function parseWitnessTypestringFields(witnessTypeString) {
|
|
3
|
+
return witnessTypeString
|
|
4
|
+
.split(",")
|
|
5
|
+
.map((field) => field.trim())
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
.map((field) => {
|
|
8
|
+
const tokens = field.split(/\s+/).filter(Boolean);
|
|
9
|
+
if (tokens.length < 2) {
|
|
10
|
+
throw new Error(`Invalid witness field "${field}" (expected "type name" in witnessTypeString)`);
|
|
11
|
+
}
|
|
12
|
+
const name = tokens[tokens.length - 1];
|
|
13
|
+
const type = tokens.slice(0, -1).join(" ");
|
|
14
|
+
return { type, name };
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function fieldNamesFromExtraTypestring(typestring) {
|
|
18
|
+
return parseWitnessTypestringFields(typestring).map((field) => field.name);
|
|
19
|
+
}
|
|
20
|
+
function uniqueFieldNames(typestrings) {
|
|
21
|
+
return [
|
|
22
|
+
...new Set(typestrings.flatMap((typestring) => fieldNamesFromExtraTypestring(typestring))),
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
function onlyInLeft(left, right) {
|
|
26
|
+
const rightSet = new Set(right);
|
|
27
|
+
return left.filter((name) => !rightSet.has(name));
|
|
28
|
+
}
|
|
29
|
+
/** Base Mandate fields shared by all Epoch intents (matches `getTaskData`). */
|
|
30
|
+
export const BASE_MANDATE_TYPESTRING = "address tokenIn,uint256 tokenInAmount,address tokenOut,uint256 minTokenOut,uint256 destinationChainId,bytes4 taskType,bytes32 protocolHashIdentifier,address recipient";
|
|
31
|
+
/** Miden → EVM cross-chain bridge witness suffix (canonical). */
|
|
32
|
+
export const MIDEN_TO_EVM_EXTRA_TYPESTRING = "string midenSourceAccount,string midenFaucetId,string midenNoteType,string midenNoteId";
|
|
33
|
+
/** EVM → Miden cross-chain bridge witness suffix (canonical). */
|
|
34
|
+
export const EVM_TO_MIDEN_EXTRA_TYPESTRING = "string midenRecipientAccount,string midenFaucetId";
|
|
35
|
+
/** Lending DEPOSIT extraData suffix (canonical). */
|
|
36
|
+
export const DEPOSIT_EXTRADATA_TYPESTRING = "string marketUid,string action,string payAsset,bool isAll";
|
|
37
|
+
/** Lending WITHDRAW extraData suffix (canonical). */
|
|
38
|
+
export const WITHDRAW_EXTRADATA_TYPESTRING = "string marketUid,string action,string payAsset,bool isAll";
|
|
39
|
+
export const MIDEN_TO_EVM_FIELD_NAMES = fieldNamesFromExtraTypestring(MIDEN_TO_EVM_EXTRA_TYPESTRING);
|
|
40
|
+
export const EVM_TO_MIDEN_FIELD_NAMES = fieldNamesFromExtraTypestring(EVM_TO_MIDEN_EXTRA_TYPESTRING);
|
|
41
|
+
/** Union of all Miden bridge field names from the canonical extra typestrings. */
|
|
42
|
+
export const MIDEN_MARKER_FIELD_NAMES = uniqueFieldNames([
|
|
43
|
+
MIDEN_TO_EVM_EXTRA_TYPESTRING,
|
|
44
|
+
EVM_TO_MIDEN_EXTRA_TYPESTRING,
|
|
45
|
+
]);
|
|
46
|
+
/** Fields declared only on the Miden→EVM extra typestring. */
|
|
47
|
+
export const MIDEN_TO_EVM_ONLY_FIELD_NAMES = onlyInLeft(MIDEN_TO_EVM_FIELD_NAMES, EVM_TO_MIDEN_FIELD_NAMES);
|
|
48
|
+
/** Fields declared only on the EVM→Miden extra typestring. */
|
|
49
|
+
export const EVM_TO_MIDEN_ONLY_FIELD_NAMES = onlyInLeft(EVM_TO_MIDEN_FIELD_NAMES, MIDEN_TO_EVM_FIELD_NAMES);
|
|
50
|
+
export const MIDEN_SOURCE_ACCOUNT_FIELD_NAME = MIDEN_TO_EVM_ONLY_FIELD_NAMES.find((name) => name === "midenSourceAccount");
|
|
51
|
+
export const MIDEN_RECIPIENT_ACCOUNT_FIELD_NAME = EVM_TO_MIDEN_ONLY_FIELD_NAMES.find((name) => name === "midenRecipientAccount");
|
|
52
|
+
function assertNoSpacesAroundCommas(typestring) {
|
|
53
|
+
if (/,(\s)|(\s),/.test(typestring)) {
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
error: 'Typestring must not contain spaces before or after commas (e.g. use "uint256 foo,uint256 bar", not "uint256 foo, uint256 bar")',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return { valid: true };
|
|
60
|
+
}
|
|
61
|
+
function assertKeysMatchTypestring(typestring, data, dataLabel = "extraData", typestringLabel = "extraDataTypestring") {
|
|
62
|
+
let fields;
|
|
63
|
+
try {
|
|
64
|
+
fields = parseWitnessTypestringFields(typestring);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
error: error instanceof Error
|
|
70
|
+
? error.message
|
|
71
|
+
: "Invalid witnessTypeString format",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const typeNames = new Set(fields.map((f) => f.name));
|
|
75
|
+
const dataKeys = new Set(Object.keys(data));
|
|
76
|
+
for (const name of typeNames) {
|
|
77
|
+
if (!dataKeys.has(name)) {
|
|
78
|
+
return {
|
|
79
|
+
valid: false,
|
|
80
|
+
error: `${dataLabel} is missing key "${name}" required by ${typestringLabel}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const key of dataKeys) {
|
|
85
|
+
if (!typeNames.has(key)) {
|
|
86
|
+
return {
|
|
87
|
+
valid: false,
|
|
88
|
+
error: `${dataLabel} has unexpected key "${key}" not declared in ${typestringLabel}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { valid: true };
|
|
93
|
+
}
|
|
94
|
+
function requiredMidenExtraTypestring(direction) {
|
|
95
|
+
return direction === "miden-to-evm"
|
|
96
|
+
? MIDEN_TO_EVM_EXTRA_TYPESTRING
|
|
97
|
+
: EVM_TO_MIDEN_EXTRA_TYPESTRING;
|
|
98
|
+
}
|
|
99
|
+
function assertMidenFieldsDeclared(typestring, requiredExtraTypestring, typestringLabel) {
|
|
100
|
+
const requiredFields = parseWitnessTypestringFields(requiredExtraTypestring);
|
|
101
|
+
let declaredFields;
|
|
102
|
+
try {
|
|
103
|
+
declaredFields = parseWitnessTypestringFields(typestring);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
valid: false,
|
|
108
|
+
error: error instanceof Error
|
|
109
|
+
? error.message
|
|
110
|
+
: "Invalid witnessTypeString format",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const declaredByName = new Map(declaredFields.map((field) => [field.name, field.type]));
|
|
114
|
+
for (const field of requiredFields) {
|
|
115
|
+
const declaredType = declaredByName.get(field.name);
|
|
116
|
+
if (declaredType === undefined) {
|
|
117
|
+
return {
|
|
118
|
+
valid: false,
|
|
119
|
+
error: `${typestringLabel} is missing required Miden field "${field.type} ${field.name}"`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (declaredType !== field.type) {
|
|
123
|
+
return {
|
|
124
|
+
valid: false,
|
|
125
|
+
error: `${typestringLabel} field "${field.name}" must be type "${field.type}", got "${declaredType}"`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { valid: true };
|
|
130
|
+
}
|
|
131
|
+
function assertMidenFieldsPresentInData(data, requiredExtraTypestring, dataLabel) {
|
|
132
|
+
const requiredNames = parseWitnessTypestringFields(requiredExtraTypestring).map((field) => field.name);
|
|
133
|
+
for (const name of requiredNames) {
|
|
134
|
+
if (!Object.prototype.hasOwnProperty.call(data, name)) {
|
|
135
|
+
return {
|
|
136
|
+
valid: false,
|
|
137
|
+
error: `${dataLabel} is missing required Miden field "${name}"`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { valid: true };
|
|
142
|
+
}
|
|
143
|
+
function validateRequiredMidenFields(typestring, data, typestringLabel, dataLabel) {
|
|
144
|
+
const direction = resolveMidenBridgeDirection(typestring, data);
|
|
145
|
+
if (direction === "conflict") {
|
|
146
|
+
return {
|
|
147
|
+
valid: false,
|
|
148
|
+
error: "Miden witness cannot include both midenSourceAccount and midenRecipientAccount",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (!direction) {
|
|
152
|
+
return {
|
|
153
|
+
valid: false,
|
|
154
|
+
error: "Unable to determine Miden bridge direction",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const requiredExtraTypestring = requiredMidenExtraTypestring(direction);
|
|
158
|
+
const declarationCheck = assertMidenFieldsDeclared(typestring, requiredExtraTypestring, typestringLabel);
|
|
159
|
+
if (!declarationCheck.valid)
|
|
160
|
+
return declarationCheck;
|
|
161
|
+
if (!data) {
|
|
162
|
+
return {
|
|
163
|
+
valid: false,
|
|
164
|
+
error: `${dataLabel} is required for Miden intents`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return assertMidenFieldsPresentInData(data, requiredExtraTypestring, dataLabel);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Validate Miden `extraDataTypestring` / `extraData` for task assembly.
|
|
171
|
+
* Required Miden fields must be declared and present; additional protocol fields
|
|
172
|
+
* are allowed when declared in the typestring.
|
|
173
|
+
*/
|
|
174
|
+
export function validateMidenExtraFields(extraDataTypestring, extraData) {
|
|
175
|
+
if (!extraDataTypestring.trim() && extraData === undefined) {
|
|
176
|
+
return { valid: true };
|
|
177
|
+
}
|
|
178
|
+
if (!isMidenIntent(extraDataTypestring, extraData)) {
|
|
179
|
+
return { valid: true };
|
|
180
|
+
}
|
|
181
|
+
return validateRequiredMidenFields(extraDataTypestring, extraData, "extraDataTypestring", "extraData");
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* When a mandate includes Miden bridge metadata, the witness typestring must
|
|
185
|
+
* declare every required Miden field for the detected direction. Additional
|
|
186
|
+
* non-Miden fields may appear before or after the Miden fields.
|
|
187
|
+
*
|
|
188
|
+
* No-op for pure EVM intents — Miden checks run only when {@link isMidenIntent}.
|
|
189
|
+
*/
|
|
190
|
+
export function assertMidenWitnessFields(witnessTypeString, mandate) {
|
|
191
|
+
if (!isMidenIntent(witnessTypeString, mandate)) {
|
|
192
|
+
return { valid: true };
|
|
193
|
+
}
|
|
194
|
+
return validateRequiredMidenFields(witnessTypeString, mandate, "witnessTypeString", "mandate");
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Validates a witness typestring against its data. Handles both the full witness
|
|
198
|
+
* (base mandate + extra suffix) and a bare extra suffix — the same rules apply to
|
|
199
|
+
* either: comma spacing, "type name" format, key alignment with the data object,
|
|
200
|
+
* and required Miden bridge fields when applicable. Pass the assembled typestring and the
|
|
201
|
+
* merged data; there is no separate extra-suffix validator.
|
|
202
|
+
*/
|
|
203
|
+
export function validateWitnessTypeString(witnessTypeString, mandate) {
|
|
204
|
+
if (mandate?.midenSourceAccount != null &&
|
|
205
|
+
mandate?.midenRecipientAccount != null) {
|
|
206
|
+
return {
|
|
207
|
+
valid: false,
|
|
208
|
+
error: "Miden witness cannot include both midenSourceAccount and midenRecipientAccount",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (!witnessTypeString || witnessTypeString.trim() === "") {
|
|
212
|
+
return { valid: true };
|
|
213
|
+
}
|
|
214
|
+
const commaCheck = assertNoSpacesAroundCommas(witnessTypeString);
|
|
215
|
+
if (!commaCheck.valid)
|
|
216
|
+
return commaCheck;
|
|
217
|
+
try {
|
|
218
|
+
parseWitnessTypestringFields(witnessTypeString);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
valid: false,
|
|
223
|
+
error: error instanceof Error
|
|
224
|
+
? error.message
|
|
225
|
+
: "Invalid witnessTypeString format",
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (mandate) {
|
|
229
|
+
const keyCheck = assertKeysMatchTypestring(witnessTypeString, mandate, "mandate", "witnessTypeString");
|
|
230
|
+
if (!keyCheck.valid)
|
|
231
|
+
return keyCheck;
|
|
232
|
+
}
|
|
233
|
+
const midenCheck = assertMidenWitnessFields(witnessTypeString, mandate);
|
|
234
|
+
if (!midenCheck.valid)
|
|
235
|
+
return midenCheck;
|
|
236
|
+
return { valid: true };
|
|
237
|
+
}
|