@openzeppelin/miden-multisig-client 0.14.8 → 0.14.9-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/dist/multisig/proposal/execution.js +3 -2
- package/dist/multisig/proposal/execution.js.map +1 -1
- package/dist/multisig.d.ts +28 -5
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +173 -5
- package/dist/multisig.js.map +1 -1
- package/dist/proposal/metadata.d.ts.map +1 -1
- package/dist/proposal/metadata.js +14 -5
- package/dist/proposal/metadata.js.map +1 -1
- package/dist/proposal/metadata.test.js +44 -0
- package/dist/proposal/metadata.test.js.map +1 -1
- package/dist/types/proposal.d.ts +16 -5
- package/dist/types/proposal.d.ts.map +1 -1
- package/dist/types/proposal.js.map +1 -1
- package/package.json +2 -2
- package/src/multisig.ts +260 -5
- package/src/proposal/metadata.test.ts +53 -1
- package/src/proposal/metadata.ts +15 -6
- package/src/types/proposal.ts +24 -9
- package/src/multisig/proposal/execution.ts +0 -385
- package/src/multisig/proposal/parser.ts +0 -191
|
@@ -44,8 +44,9 @@ export class ProposalMetadataCodec {
|
|
|
44
44
|
targetThreshold: metadata.targetThreshold,
|
|
45
45
|
signerCommitments: metadata.targetSignerCommitments,
|
|
46
46
|
};
|
|
47
|
-
case '
|
|
48
|
-
|
|
47
|
+
case 'custom':
|
|
48
|
+
// Round-trip the original server label rather than the 'custom' bucket.
|
|
49
|
+
return { ...base, proposalType: metadata.rawProposalType };
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
static fromGuardian(guardian) {
|
|
@@ -118,7 +119,13 @@ export class ProposalMetadataCodec {
|
|
|
118
119
|
targetSignerCommitments: guardian.signerCommitments,
|
|
119
120
|
};
|
|
120
121
|
default:
|
|
121
|
-
|
|
122
|
+
// Any proposal type the SDK does not model collapses to the 'custom'
|
|
123
|
+
// bucket while preserving the original label (issue #266).
|
|
124
|
+
return {
|
|
125
|
+
...base,
|
|
126
|
+
proposalType: 'custom',
|
|
127
|
+
rawProposalType: guardian.proposalType,
|
|
128
|
+
};
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
static validate(metadata) {
|
|
@@ -152,8 +159,10 @@ export class ProposalMetadataCodec {
|
|
|
152
159
|
throw new Error('p2id proposal metadata is incomplete');
|
|
153
160
|
}
|
|
154
161
|
return metadata;
|
|
155
|
-
case '
|
|
156
|
-
|
|
162
|
+
case 'custom':
|
|
163
|
+
// Custom proposals are opaque to the SDK; nothing to validate beyond
|
|
164
|
+
// the base fields. They can be listed/signed/exported, not built here.
|
|
165
|
+
return metadata;
|
|
157
166
|
}
|
|
158
167
|
}
|
|
159
168
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/proposal/metadata.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,OAAO,qBAAqB;IAChC,MAAM,CAAC,UAAU,CAAC,QAA0B;QAC1C,MAAM,IAAI,GAA6B;YACrC,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,IAAI,EAAE,QAAQ,CAAC,OAAO;YACtB,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;SAChD,CAAC;QAEF,QAAQ,QAAQ,CAAC,YAAY,EAAE,CAAC;YAC9B,KAAK,eAAe;gBAClB,OAAO;oBACL,GAAG,IAAI;oBACP,OAAO,EAAE,QAAQ,CAAC,OAAO;oBACzB,2BAA2B,EAAE,QAAQ,CAAC,eAAe;oBACrD,iBAAiB,EAAE,QAAQ,CAAC,KAAK;iBAClC,CAAC;YACJ,KAAK,MAAM;gBACT,OAAO;oBACL,GAAG,IAAI;oBACP,WAAW,EAAE,QAAQ,CAAC,WAAW;oBACjC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;oBAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;iBACxB,CAAC;YACJ,KAAK,iBAAiB;gBACpB,OAAO;oBACL,GAAG,IAAI;oBACP,eAAe,EAAE,QAAQ,CAAC,eAAe;oBACzC,iBAAiB,EAAE,QAAQ,CAAC,uBAAuB;oBACnD,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;oBAC7C,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;iBAClD,CAAC;YACJ,KAAK,4BAA4B;gBAC/B,OAAO;oBACL,GAAG,IAAI;oBACP,eAAe,EAAE,QAAQ,CAAC,eAAe;oBACzC,eAAe,EAAE,QAAQ,CAAC,eAAe;iBAC1C,CAAC;YACJ,KAAK,YAAY,CAAC;YAClB,KAAK,eAAe,CAAC;YACrB,KAAK,kBAAkB;gBACrB,OAAO;oBACL,GAAG,IAAI;oBACP,eAAe,EAAE,QAAQ,CAAC,eAAe;oBACzC,iBAAiB,EAAE,QAAQ,CAAC,uBAAuB;iBACpD,CAAC;YACJ,KAAK,
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/proposal/metadata.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,4 +40,48 @@ describe('ProposalMetadataCodec consume_notes v2 round-trip (issue #229)', () =>
|
|
|
40
40
|
expect(back.notes).toBeUndefined();
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
|
+
describe('ProposalMetadataCodec custom proposal types (issue #266)', () => {
|
|
44
|
+
it('fromGuardian collapses an unmodeled type to the custom bucket, keeping the raw label', () => {
|
|
45
|
+
const wire = {
|
|
46
|
+
proposalType: 'b2agg',
|
|
47
|
+
description: 'agglayer bridge note',
|
|
48
|
+
};
|
|
49
|
+
const md = ProposalMetadataCodec.fromGuardian(wire);
|
|
50
|
+
expect(md.proposalType).toBe('custom');
|
|
51
|
+
expect(md.rawProposalType).toBe('b2agg');
|
|
52
|
+
});
|
|
53
|
+
it('toGuardian round-trips the raw label, not the custom bucket', () => {
|
|
54
|
+
const md = {
|
|
55
|
+
proposalType: 'custom',
|
|
56
|
+
description: 'agglayer bridge note',
|
|
57
|
+
rawProposalType: 'b2agg',
|
|
58
|
+
};
|
|
59
|
+
const wire = ProposalMetadataCodec.toGuardian(md);
|
|
60
|
+
expect(wire.proposalType).toBe('b2agg');
|
|
61
|
+
const back = ProposalMetadataCodec.fromGuardian(wire);
|
|
62
|
+
expect(back.proposalType).toBe('custom');
|
|
63
|
+
expect(back.rawProposalType).toBe('b2agg');
|
|
64
|
+
});
|
|
65
|
+
it('validate accepts a custom proposal', () => {
|
|
66
|
+
const md = {
|
|
67
|
+
proposalType: 'custom',
|
|
68
|
+
description: 'opaque',
|
|
69
|
+
rawProposalType: 'b2agg',
|
|
70
|
+
};
|
|
71
|
+
expect(ProposalMetadataCodec.validate(md)).toBe(md);
|
|
72
|
+
});
|
|
73
|
+
it('round-trips update_procedure_threshold through the codec', () => {
|
|
74
|
+
const wire = {
|
|
75
|
+
proposalType: 'update_procedure_threshold',
|
|
76
|
+
targetProcedure: 'send_asset',
|
|
77
|
+
targetThreshold: 2,
|
|
78
|
+
};
|
|
79
|
+
const md = ProposalMetadataCodec.fromGuardian(wire);
|
|
80
|
+
expect(md.proposalType).toBe('update_procedure_threshold');
|
|
81
|
+
const back = ProposalMetadataCodec.toGuardian(md);
|
|
82
|
+
expect(back.proposalType).toBe('update_procedure_threshold');
|
|
83
|
+
expect(back.targetProcedure).toBe('send_asset');
|
|
84
|
+
expect(back.targetThreshold).toBe(2);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
43
87
|
//# sourceMappingURL=metadata.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.test.js","sourceRoot":"","sources":["../../src/proposal/metadata.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"metadata.test.js","sourceRoot":"","sources":["../../src/proposal/metadata.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAMtD,QAAQ,CAAC,gEAAgE,EAAE,GAAG,EAAE;IAC9E,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,EAAE,GAAiC;YACvC,YAAY,EAAE,eAAe;YAC7B,WAAW,EAAE,kBAAkB;YAC/B,OAAO,EAAE,CAAC,OAAO,CAAC;YAClB,eAAe,EAAE,CAAC;YAClB,KAAK,EAAE,CAAC,kBAAkB,CAAC;SAC5B,CAAC;QACF,MAAM,IAAI,GAAG,qBAAqB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,IAAI,GAA6B;YACrC,YAAY,EAAE,eAAe;YAC7B,OAAO,EAAE,CAAC,OAAO,CAAC;YAClB,2BAA2B,EAAE,CAAC;YAC9B,iBAAiB,EAAE,CAAC,kBAAkB,CAAC;SACxC,CAAC;QACF,MAAM,EAAE,GAAG,qBAAqB,CAAC,YAAY,CAAC,IAAI,CAAiC,CAAC;QACpF,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC9C,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,EAAE,GAAiC;YACvC,YAAY,EAAE,eAAe;YAC7B,WAAW,EAAE,QAAQ;YACrB,OAAO,EAAE,CAAC,OAAO,CAAC;SACnB,CAAC;QACF,MAAM,IAAI,GAAG,qBAAqB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,aAAa,EAAE,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,aAAa,EAAE,CAAC;QAC/C,MAAM,IAAI,GAAG,qBAAqB,CAAC,YAAY,CAAC,IAAI,CAAiC,CAAC;QACtF,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACxE,EAAE,CAAC,sFAAsF,EAAE,GAAG,EAAE;QAC9F,MAAM,IAAI,GAA6B;YACrC,YAAY,EAAE,OAAO;YACrB,WAAW,EAAE,sBAAsB;SACpC,CAAC;QACF,MAAM,EAAE,GAAG,qBAAqB,CAAC,YAAY,CAAC,IAAI,CAA2B,CAAC;QAC9E,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,EAAE,GAA2B;YACjC,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,sBAAsB;YACnC,eAAe,EAAE,OAAO;SACzB,CAAC;QACF,MAAM,IAAI,GAAG,qBAAqB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAExC,MAAM,IAAI,GAAG,qBAAqB,CAAC,YAAY,CAAC,IAAI,CAA2B,CAAC;QAChF,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,EAAE,GAA2B;YACjC,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,QAAQ;YACrB,eAAe,EAAE,OAAO;SACzB,CAAC;QACF,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,IAAI,GAA6B;YACrC,YAAY,EAAE,4BAA4B;YAC1C,eAAe,EAAE,YAAY;YAC7B,eAAe,EAAE,CAAC;SACnB,CAAC;QACF,MAAM,EAAE,GAAG,qBAAqB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACpD,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,qBAAqB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC7D,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/types/proposal.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import type { ProposalSignature,
|
|
1
|
+
import type { ProposalSignature, SignatureScheme } from '@openzeppelin/guardian-client';
|
|
2
2
|
import type { ProcedureName } from '../procedures.js';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Closed set of proposal types the multisig SDK models behaviorally, plus the
|
|
5
|
+
* `'custom'` bucket for any server-defined type the SDK does not model (issue
|
|
6
|
+
* #266). Defined explicitly (not derived from the now-arbitrary guardian-client
|
|
7
|
+
* wire union) so the exhaustive switches in the metadata codec stay sound.
|
|
8
|
+
*/
|
|
9
|
+
export type ProposalType = 'add_signer' | 'remove_signer' | 'change_threshold' | 'update_procedure_threshold' | 'switch_guardian' | 'consume_notes' | 'p2id' | 'custom';
|
|
4
10
|
export type ProposalStatus = 'pending' | 'ready' | 'finalized';
|
|
5
11
|
export type TransactionProposalStatus = {
|
|
6
12
|
type: 'pending';
|
|
@@ -61,10 +67,15 @@ export interface P2IdProposalMetadata extends BaseProposalMetadata {
|
|
|
61
67
|
faucetId: string;
|
|
62
68
|
amount: string;
|
|
63
69
|
}
|
|
64
|
-
export interface
|
|
65
|
-
proposalType: '
|
|
70
|
+
export interface CustomProposalMetadata extends BaseProposalMetadata {
|
|
71
|
+
proposalType: 'custom';
|
|
72
|
+
/** Original server-defined proposal label, e.g. "b2agg" (issue #266). Mirrors
|
|
73
|
+
* Rust `ProposalMetadata.proposal_type`; it is what lets a custom proposal
|
|
74
|
+
* round-trip back to GUARDIAN/export, so it is required in the domain model.
|
|
75
|
+
* Any wire-level optionality is resolved in the parser/codec boundary. */
|
|
76
|
+
rawProposalType: string;
|
|
66
77
|
}
|
|
67
|
-
export type ProposalMetadata = UpdateSignersProposalMetadata | SwitchGuardianProposalMetadata | UpdateProcedureThresholdProposalMetadata | ConsumeNotesProposalMetadata | P2IdProposalMetadata |
|
|
78
|
+
export type ProposalMetadata = UpdateSignersProposalMetadata | SwitchGuardianProposalMetadata | UpdateProcedureThresholdProposalMetadata | ConsumeNotesProposalMetadata | P2IdProposalMetadata | CustomProposalMetadata;
|
|
68
79
|
export interface Proposal {
|
|
69
80
|
id: string;
|
|
70
81
|
accountId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal.d.ts","sourceRoot":"","sources":["../../src/types/proposal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"proposal.d.ts","sourceRoot":"","sources":["../../src/types/proposal.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../src/types/proposal.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../src/types/proposal.ts"],"names":[],"mappings":"AA6DA,gFAAgF;AAChF,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAU,CAAC;AAE5D,iEAAiE;AACjE,MAAM,CAAC,MAAM,gCAAgC,GAAG,GAAG,GAAG,IAAI,CAAC;AAW3D,MAAM,UAAU,gBAAgB,CAAC,EAAgC;IAC/D,OAAO,EAAE,CAAC,eAAe,KAAK,iCAAiC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAgC;IAC/D,OAAO,EAAE,CAAC,eAAe,KAAK,SAAS,IAAI,EAAE,CAAC,eAAe,KAAK,CAAC,CAAC;AACtE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openzeppelin/miden-multisig-client",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.9-rc.0",
|
|
4
4
|
"description": "TypeScript SDK for Miden multisig accounts with GUARDIAN integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@noble/curves": "^1.9.7",
|
|
31
31
|
"@miden-sdk/miden-sdk": "0.14.5",
|
|
32
32
|
"@noble/hashes": "^2.0.1",
|
|
33
|
-
"@openzeppelin/guardian-client": "^0.14.
|
|
33
|
+
"@openzeppelin/guardian-client": "^0.14.9-rc.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"typescript": "^5.7.2",
|
package/src/multisig.ts
CHANGED
|
@@ -13,13 +13,13 @@ import type {
|
|
|
13
13
|
NoteAsset,
|
|
14
14
|
Proposal,
|
|
15
15
|
ProposalMetadata,
|
|
16
|
+
ProposalSignatureEntry,
|
|
16
17
|
ProposalType,
|
|
17
18
|
} from './types.js';
|
|
18
19
|
import type { ProcedureName } from './procedures.js';
|
|
19
20
|
import type {
|
|
20
21
|
MidenClient,
|
|
21
22
|
TransactionProver,
|
|
22
|
-
TransactionRequest,
|
|
23
23
|
WasmWebClient,
|
|
24
24
|
} from '@miden-sdk/miden-sdk';
|
|
25
25
|
import {
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
Note,
|
|
32
32
|
RpcClient,
|
|
33
33
|
Signature,
|
|
34
|
+
TransactionRequest,
|
|
34
35
|
TransactionSummary,
|
|
35
36
|
Word,
|
|
36
37
|
} from '@miden-sdk/miden-sdk';
|
|
@@ -97,6 +98,32 @@ export interface AccountStateVerificationResult {
|
|
|
97
98
|
/**
|
|
98
99
|
* Represents a multisig account with GUARDIAN integration.
|
|
99
100
|
*/
|
|
101
|
+
const BUILTIN_PROPOSAL_TYPES = new Set<string>([
|
|
102
|
+
'add_signer',
|
|
103
|
+
'remove_signer',
|
|
104
|
+
'change_threshold',
|
|
105
|
+
'update_procedure_threshold',
|
|
106
|
+
'switch_guardian',
|
|
107
|
+
'consume_notes',
|
|
108
|
+
'p2id',
|
|
109
|
+
// Reserved: the SDK's internal bucket name for unmodeled types. A producer
|
|
110
|
+
// must not use it as a custom label, or it would collide with the bucket.
|
|
111
|
+
'custom',
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Deserialize producer-supplied transaction request bytes, wrapping any failure
|
|
116
|
+
* in a stable message that mirrors the Rust SDK's `deserialize_transaction_request`.
|
|
117
|
+
*/
|
|
118
|
+
function deserializeTransactionRequest(bytes: Uint8Array): TransactionRequest {
|
|
119
|
+
try {
|
|
120
|
+
return TransactionRequest.deserialize(bytes);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
123
|
+
throw new Error(`failed to decode transaction request: ${detail}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
100
127
|
export class Multisig {
|
|
101
128
|
account: Account;
|
|
102
129
|
threshold: number;
|
|
@@ -977,6 +1004,220 @@ export class Multisig {
|
|
|
977
1004
|
proposal.status = 'finalized';
|
|
978
1005
|
}
|
|
979
1006
|
|
|
1007
|
+
/**
|
|
1008
|
+
* Submit an integration-built transaction (advice already injected). Mirrors
|
|
1009
|
+
* the Rust `submit_transaction`; used by the custom proposal producer flow
|
|
1010
|
+
* after `prepareCustomExecution` rebuilds its request with the returned advice.
|
|
1011
|
+
*/
|
|
1012
|
+
async submitTransaction(request: TransactionRequest): Promise<void> {
|
|
1013
|
+
await this.midenClient.transactions.submit(AccountId.fromHex(this._accountId), request);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Create a proposal from a producer-built transaction the SDK does not model
|
|
1018
|
+
* (issue #266 producer API). `transactionRequestBytes` is a serialized TransactionRequest;
|
|
1019
|
+
* `proposalType` is a free-form, non-empty label that must not collide with a
|
|
1020
|
+
* built-in type. The integration keeps its own recipe to execute later via
|
|
1021
|
+
* `prepareCustomExecution`.
|
|
1022
|
+
*/
|
|
1023
|
+
async createCustomProposal(
|
|
1024
|
+
transactionRequestBytes: Uint8Array,
|
|
1025
|
+
proposalType: string,
|
|
1026
|
+
nonce?: number,
|
|
1027
|
+
): Promise<Proposal> {
|
|
1028
|
+
const label = proposalType.trim().toLowerCase();
|
|
1029
|
+
if (label.length === 0) {
|
|
1030
|
+
throw new Error('proposalType must not be empty');
|
|
1031
|
+
}
|
|
1032
|
+
if (!/^[a-z0-9_]+$/.test(label)) {
|
|
1033
|
+
throw new Error(
|
|
1034
|
+
`proposalType '${label}' must be lowercase snake_case ([a-z0-9_]): no spaces, hyphens, or other characters`,
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
if (BUILTIN_PROPOSAL_TYPES.has(label)) {
|
|
1038
|
+
throw new Error(
|
|
1039
|
+
`'${label}' is a built-in proposal type; use the typed proposal API instead`,
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const webClient = await this.getRawClient();
|
|
1044
|
+
const request = deserializeTransactionRequest(transactionRequestBytes);
|
|
1045
|
+
const summary = await executeForSummary(webClient, this._accountId, request);
|
|
1046
|
+
const summaryBase64 = uint8ArrayToBase64(summary.serialize());
|
|
1047
|
+
const proposalNonce = nonce ?? Date.now();
|
|
1048
|
+
|
|
1049
|
+
const metadata: ProposalMetadata = {
|
|
1050
|
+
proposalType: 'custom',
|
|
1051
|
+
description: '',
|
|
1052
|
+
rawProposalType: label,
|
|
1053
|
+
requiredSignatures: this.getEffectiveThreshold('custom'),
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
return this.createProposal(proposalNonce, summaryBase64, metadata);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Assemble the validated execution advice (cosigner signatures + GUARDIAN
|
|
1061
|
+
* acknowledgment) for a ready custom proposal, so an integration can rebuild
|
|
1062
|
+
* its transaction with its own recipe and submit (issue #266 producer API).
|
|
1063
|
+
*
|
|
1064
|
+
* `transactionRequestBytes` is the serialized transaction request; it is used only to verify
|
|
1065
|
+
* (binding check) that it reproduces the signed commitment, before the
|
|
1066
|
+
* acknowledgment is requested. Returns the advice the integration folds into
|
|
1067
|
+
* its rebuilt transaction (`builder.extendAdviceMap(advice)`).
|
|
1068
|
+
*/
|
|
1069
|
+
async prepareCustomExecution(
|
|
1070
|
+
proposalId: string,
|
|
1071
|
+
transactionRequestBytes: Uint8Array,
|
|
1072
|
+
): Promise<AdviceMap> {
|
|
1073
|
+
const normalizedProposalId = normalizeHexWord(proposalId);
|
|
1074
|
+
const delta = await this.guardian.getDeltaProposal(this._accountId, normalizedProposalId);
|
|
1075
|
+
const existing = this.getLocalProposal(proposalId);
|
|
1076
|
+
const proposal = this.proposalFactory().fromDelta(
|
|
1077
|
+
delta,
|
|
1078
|
+
normalizedProposalId,
|
|
1079
|
+
existing?.metadata,
|
|
1080
|
+
existing?.signatures ?? [],
|
|
1081
|
+
);
|
|
1082
|
+
|
|
1083
|
+
if (proposal.metadata.proposalType !== 'custom') {
|
|
1084
|
+
throw new Error(
|
|
1085
|
+
'prepareCustomExecution is only for custom proposals; use executeProposal for built-in types',
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const effectiveThreshold = this.getEffectiveThreshold('custom');
|
|
1090
|
+
const signaturesForExecution = new ProposalSignatures(
|
|
1091
|
+
proposal.signatures,
|
|
1092
|
+
this.signerCommitments,
|
|
1093
|
+
`Invalid proposal signatures for ${proposalId}`,
|
|
1094
|
+
).entries();
|
|
1095
|
+
if (signaturesForExecution.length < effectiveThreshold) {
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`Proposal is not ready for execution: have ${signaturesForExecution.length} of ${effectiveThreshold} required signatures.`,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const txSummary = TransactionSummary.deserialize(
|
|
1102
|
+
base64ToUint8Array(delta.deltaPayload.txSummary.data),
|
|
1103
|
+
);
|
|
1104
|
+
const signedCommitmentHex = normalizeHexWord(txSummary.toCommitment().toHex());
|
|
1105
|
+
|
|
1106
|
+
const bindingRequest = deserializeTransactionRequest(transactionRequestBytes);
|
|
1107
|
+
|
|
1108
|
+
const webClient = await this.getRawClient();
|
|
1109
|
+
const derived = await executeForSummary(webClient, this._accountId, bindingRequest);
|
|
1110
|
+
const derivedCommitmentHex = normalizeHexWord(derived.toCommitment().toHex());
|
|
1111
|
+
if (derivedCommitmentHex !== signedCommitmentHex) {
|
|
1112
|
+
throw new Error(
|
|
1113
|
+
`Custom proposal binding mismatch: expected ${signedCommitmentHex}, got ${derivedCommitmentHex}`,
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
return this.assembleCustomAdvice(
|
|
1118
|
+
proposalId,
|
|
1119
|
+
signaturesForExecution,
|
|
1120
|
+
signedCommitmentHex,
|
|
1121
|
+
delta,
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
private async assembleCustomAdvice(
|
|
1126
|
+
proposalId: string,
|
|
1127
|
+
signaturesForExecution: ProposalSignatureEntry[],
|
|
1128
|
+
normalizedTxCommitmentHex: string,
|
|
1129
|
+
delta: DeltaObject,
|
|
1130
|
+
): Promise<AdviceMap> {
|
|
1131
|
+
const normalizedSignerCommitments = new Set(
|
|
1132
|
+
this.signerCommitments.map((commitment) => normalizeHexWord(commitment)),
|
|
1133
|
+
);
|
|
1134
|
+
const adviceMap = new AdviceMap();
|
|
1135
|
+
const adviceMapKeys = new Set<string>();
|
|
1136
|
+
const createTxCommitmentWord = (): Word => Word.fromHex(normalizedTxCommitmentHex);
|
|
1137
|
+
|
|
1138
|
+
for (const cosignerSig of signaturesForExecution) {
|
|
1139
|
+
let signerCommitmentHex = normalizeHexWord(cosignerSig.signerId);
|
|
1140
|
+
const ecdsaPublicKey =
|
|
1141
|
+
cosignerSig.signature.scheme === 'ecdsa' ? cosignerSig.signature.publicKey : undefined;
|
|
1142
|
+
|
|
1143
|
+
if (cosignerSig.signature.scheme === 'ecdsa') {
|
|
1144
|
+
if (!ecdsaPublicKey) {
|
|
1145
|
+
throw new Error(
|
|
1146
|
+
`ECDSA proposal signature for ${signerCommitmentHex} is missing publicKey`,
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
const derivedCommitment = tryComputeEcdsaCommitmentHex(ecdsaPublicKey);
|
|
1150
|
+
if (derivedCommitment && derivedCommitment !== signerCommitmentHex) {
|
|
1151
|
+
if (!normalizedSignerCommitments.has(derivedCommitment)) {
|
|
1152
|
+
throw new Error(
|
|
1153
|
+
`ECDSA public key commitment mismatch: derived commitment ${derivedCommitment} is not in signerCommitments.`,
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
signerCommitmentHex = derivedCommitment;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const signerCommitment = Word.fromHex(signerCommitmentHex);
|
|
1161
|
+
const sigBytes = signatureHexToBytes(
|
|
1162
|
+
cosignerSig.signature.signature,
|
|
1163
|
+
cosignerSig.signature.scheme,
|
|
1164
|
+
);
|
|
1165
|
+
const signature = Signature.deserialize(sigBytes);
|
|
1166
|
+
const { key, values } = buildSignatureAdviceEntry(
|
|
1167
|
+
signerCommitment,
|
|
1168
|
+
createTxCommitmentWord(),
|
|
1169
|
+
signature,
|
|
1170
|
+
ecdsaPublicKey,
|
|
1171
|
+
cosignerSig.signature.scheme === 'ecdsa' ? cosignerSig.signature.signature : undefined,
|
|
1172
|
+
);
|
|
1173
|
+
const keyHex = normalizeHexWord(key.toHex());
|
|
1174
|
+
if (adviceMapKeys.has(keyHex)) {
|
|
1175
|
+
throw new Error(`Duplicate advice-map key detected for proposal ${proposalId}`);
|
|
1176
|
+
}
|
|
1177
|
+
adviceMapKeys.add(keyHex);
|
|
1178
|
+
adviceMap.insert(key, new FeltArray(values));
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const executionDelta = { ...delta, deltaPayload: delta.deltaPayload.txSummary };
|
|
1182
|
+
const pushResult = await this.guardian.pushDelta(executionDelta);
|
|
1183
|
+
const ackSigHex = pushResult.ackSig;
|
|
1184
|
+
if (!ackSigHex) {
|
|
1185
|
+
throw new Error('GUARDIAN did not return acknowledgment signature');
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const guardianCommitment = Word.fromHex(normalizeHexWord(this.guardianCommitment));
|
|
1189
|
+
const ackScheme = (pushResult.ackScheme as 'ecdsa' | 'falcon') || this.signer.scheme;
|
|
1190
|
+
const ackPubkey = pushResult.ackPubkey || this.guardianPublicKey;
|
|
1191
|
+
if (ackScheme === 'ecdsa' && !ackPubkey) {
|
|
1192
|
+
throw new Error('GUARDIAN acknowledgment is missing ECDSA public key');
|
|
1193
|
+
}
|
|
1194
|
+
if (ackScheme === 'ecdsa' && ackPubkey) {
|
|
1195
|
+
const derivedCommitment = tryComputeEcdsaCommitmentHex(ackPubkey);
|
|
1196
|
+
if (derivedCommitment && derivedCommitment !== normalizeHexWord(this.guardianCommitment)) {
|
|
1197
|
+
throw new Error('GUARDIAN public key commitment mismatch');
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
const ackSigBytes = signatureHexToBytes(ackSigHex, ackScheme);
|
|
1201
|
+
const ackSignature = Signature.deserialize(ackSigBytes);
|
|
1202
|
+
const { key: ackKey, values: ackValues } = buildSignatureAdviceEntry(
|
|
1203
|
+
guardianCommitment,
|
|
1204
|
+
createTxCommitmentWord(),
|
|
1205
|
+
ackSignature,
|
|
1206
|
+
ackScheme === 'ecdsa' ? ackPubkey : undefined,
|
|
1207
|
+
ackScheme === 'ecdsa' ? ackSigHex : undefined,
|
|
1208
|
+
);
|
|
1209
|
+
const ackKeyHex = normalizeHexWord(ackKey.toHex());
|
|
1210
|
+
if (adviceMapKeys.has(ackKeyHex)) {
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
`Duplicate advice-map key detected for GUARDIAN acknowledgment in proposal ${proposalId}`,
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
adviceMapKeys.add(ackKeyHex);
|
|
1216
|
+
adviceMap.insert(ackKey, new FeltArray(ackValues));
|
|
1217
|
+
|
|
1218
|
+
return adviceMap;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
980
1221
|
private getLocalProposal(proposalId: string): Proposal | undefined {
|
|
981
1222
|
const normalizedProposalId = normalizeHexWord(proposalId);
|
|
982
1223
|
return this.proposals.get(proposalId) ?? this.proposals.get(normalizedProposalId);
|
|
@@ -994,6 +1235,15 @@ export class Multisig {
|
|
|
994
1235
|
await this.verifyProposalMetadataBinding(proposal);
|
|
995
1236
|
|
|
996
1237
|
const metadata = proposal.metadata;
|
|
1238
|
+
// Reject custom proposals before any advice assembly or GUARDIAN ack push:
|
|
1239
|
+
// the SDK cannot rebuild an opaque custom transaction, and the rejection
|
|
1240
|
+
// must stay side-effect free (mirrors the Rust early guard in execute_proposal).
|
|
1241
|
+
if (metadata.proposalType === 'custom') {
|
|
1242
|
+
throw new Error(
|
|
1243
|
+
'Cannot execute a custom proposal via executeProposal; use prepareCustomExecution to ' +
|
|
1244
|
+
'get the cosigner + GUARDIAN advice, then submitTransaction with your rebuilt request (issue #266).',
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
997
1247
|
const effectiveThreshold = this.getEffectiveThreshold(metadata.proposalType);
|
|
998
1248
|
const signatureContext = `Invalid proposal signatures for ${proposalId}`;
|
|
999
1249
|
const signaturesForExecution = new ProposalSignatures(
|
|
@@ -1299,8 +1549,11 @@ export class Multisig {
|
|
|
1299
1549
|
|
|
1300
1550
|
private async verifyProposalMetadataBinding(proposal: Proposal): Promise<string> {
|
|
1301
1551
|
const txSummaryCommitment = this.ensureProposalCommitmentMatchesSummary(proposal);
|
|
1302
|
-
if (proposal.metadata.proposalType === '
|
|
1303
|
-
|
|
1552
|
+
if (proposal.metadata.proposalType === 'custom') {
|
|
1553
|
+
// Custom proposals (issue #266) have no per-type reconstruction recipe;
|
|
1554
|
+
// the id ↔ tx_summary commitment match above is the only available
|
|
1555
|
+
// integrity guarantee for an opaque proposal.
|
|
1556
|
+
return txSummaryCommitment;
|
|
1304
1557
|
}
|
|
1305
1558
|
|
|
1306
1559
|
const summary = TransactionSummary.deserialize(base64ToUint8Array(proposal.txSummary));
|
|
@@ -1410,8 +1663,10 @@ export class Multisig {
|
|
|
1410
1663
|
);
|
|
1411
1664
|
return request;
|
|
1412
1665
|
}
|
|
1413
|
-
case '
|
|
1414
|
-
throw new Error(
|
|
1666
|
+
case 'custom':
|
|
1667
|
+
throw new Error(
|
|
1668
|
+
`Cannot build a transaction for a custom proposal type: ${metadata.rawProposalType ?? 'custom'}`,
|
|
1669
|
+
);
|
|
1415
1670
|
}
|
|
1416
1671
|
}
|
|
1417
1672
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import type { ProposalMetadata as GuardianProposalMetadata } from '@openzeppelin/guardian-client';
|
|
3
3
|
import { ProposalMetadataCodec } from './metadata.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ConsumeNotesProposalMetadata,
|
|
6
|
+
CustomProposalMetadata,
|
|
7
|
+
} from '../types/proposal.js';
|
|
5
8
|
|
|
6
9
|
describe('ProposalMetadataCodec consume_notes v2 round-trip (issue #229)', () => {
|
|
7
10
|
it('toGuardian threads metadataVersion and notes to the wire', () => {
|
|
@@ -45,3 +48,52 @@ describe('ProposalMetadataCodec consume_notes v2 round-trip (issue #229)', () =>
|
|
|
45
48
|
expect(back.notes).toBeUndefined();
|
|
46
49
|
});
|
|
47
50
|
});
|
|
51
|
+
|
|
52
|
+
describe('ProposalMetadataCodec custom proposal types (issue #266)', () => {
|
|
53
|
+
it('fromGuardian collapses an unmodeled type to the custom bucket, keeping the raw label', () => {
|
|
54
|
+
const wire: GuardianProposalMetadata = {
|
|
55
|
+
proposalType: 'b2agg',
|
|
56
|
+
description: 'agglayer bridge note',
|
|
57
|
+
};
|
|
58
|
+
const md = ProposalMetadataCodec.fromGuardian(wire) as CustomProposalMetadata;
|
|
59
|
+
expect(md.proposalType).toBe('custom');
|
|
60
|
+
expect(md.rawProposalType).toBe('b2agg');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('toGuardian round-trips the raw label, not the custom bucket', () => {
|
|
64
|
+
const md: CustomProposalMetadata = {
|
|
65
|
+
proposalType: 'custom',
|
|
66
|
+
description: 'agglayer bridge note',
|
|
67
|
+
rawProposalType: 'b2agg',
|
|
68
|
+
};
|
|
69
|
+
const wire = ProposalMetadataCodec.toGuardian(md);
|
|
70
|
+
expect(wire.proposalType).toBe('b2agg');
|
|
71
|
+
|
|
72
|
+
const back = ProposalMetadataCodec.fromGuardian(wire) as CustomProposalMetadata;
|
|
73
|
+
expect(back.proposalType).toBe('custom');
|
|
74
|
+
expect(back.rawProposalType).toBe('b2agg');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('validate accepts a custom proposal', () => {
|
|
78
|
+
const md: CustomProposalMetadata = {
|
|
79
|
+
proposalType: 'custom',
|
|
80
|
+
description: 'opaque',
|
|
81
|
+
rawProposalType: 'b2agg',
|
|
82
|
+
};
|
|
83
|
+
expect(ProposalMetadataCodec.validate(md)).toBe(md);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('round-trips update_procedure_threshold through the codec', () => {
|
|
87
|
+
const wire: GuardianProposalMetadata = {
|
|
88
|
+
proposalType: 'update_procedure_threshold',
|
|
89
|
+
targetProcedure: 'send_asset',
|
|
90
|
+
targetThreshold: 2,
|
|
91
|
+
};
|
|
92
|
+
const md = ProposalMetadataCodec.fromGuardian(wire);
|
|
93
|
+
expect(md.proposalType).toBe('update_procedure_threshold');
|
|
94
|
+
const back = ProposalMetadataCodec.toGuardian(md);
|
|
95
|
+
expect(back.proposalType).toBe('update_procedure_threshold');
|
|
96
|
+
expect(back.targetProcedure).toBe('send_asset');
|
|
97
|
+
expect(back.targetThreshold).toBe(2);
|
|
98
|
+
});
|
|
99
|
+
});
|
package/src/proposal/metadata.ts
CHANGED
|
@@ -48,8 +48,9 @@ export class ProposalMetadataCodec {
|
|
|
48
48
|
targetThreshold: metadata.targetThreshold,
|
|
49
49
|
signerCommitments: metadata.targetSignerCommitments,
|
|
50
50
|
};
|
|
51
|
-
case '
|
|
52
|
-
|
|
51
|
+
case 'custom':
|
|
52
|
+
// Round-trip the original server label rather than the 'custom' bucket.
|
|
53
|
+
return { ...base, proposalType: metadata.rawProposalType };
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -120,12 +121,18 @@ export class ProposalMetadataCodec {
|
|
|
120
121
|
}
|
|
121
122
|
return {
|
|
122
123
|
...base,
|
|
123
|
-
proposalType: guardian.proposalType,
|
|
124
|
+
proposalType: guardian.proposalType as 'add_signer' | 'remove_signer' | 'change_threshold',
|
|
124
125
|
targetThreshold: guardian.targetThreshold,
|
|
125
126
|
targetSignerCommitments: guardian.signerCommitments,
|
|
126
127
|
};
|
|
127
128
|
default:
|
|
128
|
-
|
|
129
|
+
// Any proposal type the SDK does not model collapses to the 'custom'
|
|
130
|
+
// bucket while preserving the original label (issue #266).
|
|
131
|
+
return {
|
|
132
|
+
...base,
|
|
133
|
+
proposalType: 'custom',
|
|
134
|
+
rawProposalType: guardian.proposalType,
|
|
135
|
+
};
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
|
|
@@ -162,8 +169,10 @@ export class ProposalMetadataCodec {
|
|
|
162
169
|
throw new Error('p2id proposal metadata is incomplete');
|
|
163
170
|
}
|
|
164
171
|
return metadata;
|
|
165
|
-
case '
|
|
166
|
-
|
|
172
|
+
case 'custom':
|
|
173
|
+
// Custom proposals are opaque to the SDK; nothing to validate beyond
|
|
174
|
+
// the base fields. They can be listed/signed/exported, not built here.
|
|
175
|
+
return metadata;
|
|
167
176
|
}
|
|
168
177
|
}
|
|
169
178
|
}
|