@miden-sdk/miden-para 0.10.10
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 +49 -0
- package/dist/cjs/index.js +17 -0
- package/dist/cjs/midenClient.js +128 -0
- package/dist/cjs/modalClient.js +252 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/types.js +15 -0
- package/dist/cjs/utils.js +127 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/midenClient.js +107 -0
- package/dist/esm/modalClient.js +233 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/types.js +0 -0
- package/dist/esm/utils.js +98 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/midenClient.d.ts +18 -0
- package/dist/types/modalClient.d.ts +11 -0
- package/dist/types/types.d.ts +34 -0
- package/dist/types/utils.d.ts +23 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Miden
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @miden-sdk/miden-para
|
|
2
|
+
|
|
3
|
+
[](https://github.com/0xMiden/miden-para/blob/main/LICENSE)
|
|
4
|
+
[](https://github.com/0xMiden/miden-para/actions/workflows/test.yml)
|
|
5
|
+
[](https://github.com/0xMiden/miden-para/actions/workflows/build.yml)
|
|
6
|
+
|
|
7
|
+
This is the Miden x Para SDK integration. Below, you'll find instructions for local building and linking the library. If you're looking for React integration, see [Miden x Para x React](./packages/use-miden-para-react/README.md) (package `@miden-sdk/use-miden-para-react`). If you want to scaffold a fresh Vite `react-ts` app with our Vite config baked in, check [create-miden-para-react](./packages/create-miden-para-react/README.md) (package `@miden-sdk/create-miden-para-react`).
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- **Yarn 1.22.22** (enforced via `packageManager` field)
|
|
12
|
+
- Node.js (compatible with your project)
|
|
13
|
+
- A Para API key. **Production deployments require a Para production API key**; use a non-prod key for local/dev.
|
|
14
|
+
|
|
15
|
+
This project uses Yarn 1.22.22. The version is locked in `package.json` and will be automatically enforced by modern package managers that support the `packageManager` field.
|
|
16
|
+
|
|
17
|
+
## Peer Dependencies
|
|
18
|
+
|
|
19
|
+
`@miden-sdk/miden-para` expects these packages to be provided by the consuming app. Install matching versions alongside this package to avoid duplicate copies:
|
|
20
|
+
|
|
21
|
+
- `@demox-labs/miden-sdk@^0.12.5`
|
|
22
|
+
- `@getpara/web-sdk@2.0.0-alpha.73`
|
|
23
|
+
|
|
24
|
+
Example install:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
yarn add @miden-sdk/miden-para @demox-labs/miden-sdk@^0.12.5 @getpara/web-sdk@2.0.0-alpha.73
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
When creating a client with `storageMode` set to `private`, supply an `accountSeed`; the initializer will throw if it is missing so that private accounts remain recoverable.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
yarn install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Building
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
yarn build
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Publishing to npm
|
|
45
|
+
|
|
46
|
+
1. Update the version in `package.json` (the published package is `@miden-sdk/miden-para`).
|
|
47
|
+
2. Authenticate with npm if needed: `npm login`.
|
|
48
|
+
3. Publish: `npm run publish`. The `prepack` hook rebuilds `dist/` and the `postpack` hook moves the generated tarball into `build/`.
|
|
49
|
+
4. (Optional) Inspect the packed artifact without publishing via `npm pack` and check `build/` for the resulting `miden-sdk-miden-para-<version>.tgz`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __copyProps = (to, from, except, desc) => {
|
|
6
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
7
|
+
for (let key of __getOwnPropNames(from))
|
|
8
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
9
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
10
|
+
}
|
|
11
|
+
return to;
|
|
12
|
+
};
|
|
13
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var index_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(index_exports);
|
|
17
|
+
__reExport(index_exports, require("./midenClient.js"), module.exports);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var midenClient_exports = {};
|
|
29
|
+
__export(midenClient_exports, {
|
|
30
|
+
createParaMidenClient: () => createParaMidenClient,
|
|
31
|
+
signCb: () => signCb
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(midenClient_exports);
|
|
34
|
+
var import_web_sdk = require("@getpara/web-sdk");
|
|
35
|
+
var import_sha3 = require("@noble/hashes/sha3.js");
|
|
36
|
+
var import_utils = require("./utils.js");
|
|
37
|
+
var import_utils2 = require("@noble/hashes/utils.js");
|
|
38
|
+
var import_modalClient = require("./modalClient.js");
|
|
39
|
+
const signCb = (para, wallet, showSigningModal, customSignConfirmStep) => {
|
|
40
|
+
return async (_, signingInputs) => {
|
|
41
|
+
const { SigningInputs } = await import("@demox-labs/miden-sdk");
|
|
42
|
+
const inputs = SigningInputs.deserialize(signingInputs);
|
|
43
|
+
let commitment = inputs.toCommitment().toHex().slice(2);
|
|
44
|
+
const hashed = (0, import_utils2.bytesToHex)((0, import_sha3.keccak_256)((0, import_utils2.hexToBytes)(commitment)));
|
|
45
|
+
const txSummaryJson = (0, import_utils.txSummaryToJosn)(inputs.transactionSummaryPayload());
|
|
46
|
+
if (showSigningModal) {
|
|
47
|
+
const confirmed = await (0, import_modalClient.signingModal)(txSummaryJson);
|
|
48
|
+
if (!confirmed) {
|
|
49
|
+
throw new Error("User cancelled signing");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (customSignConfirmStep) {
|
|
53
|
+
await customSignConfirmStep(txSummaryJson);
|
|
54
|
+
}
|
|
55
|
+
console.time("Para Signing Time");
|
|
56
|
+
const res = await para.signMessage({
|
|
57
|
+
walletId: wallet.id,
|
|
58
|
+
messageBase64: (0, import_web_sdk.hexStringToBase64)(hashed)
|
|
59
|
+
});
|
|
60
|
+
console.timeEnd("Para Signing Time");
|
|
61
|
+
const signature = res.signature;
|
|
62
|
+
const sig = (0, import_utils.fromHexSig)(signature);
|
|
63
|
+
return sig;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
async function createAccount(midenClient, publicKey, opts) {
|
|
67
|
+
const { AccountBuilder, AccountComponent, AccountStorageMode } = await import("@demox-labs/miden-sdk");
|
|
68
|
+
await midenClient.syncState();
|
|
69
|
+
let pkc = await (0, import_utils.evmPkToCommitment)(publicKey);
|
|
70
|
+
const accountBuilder = new AccountBuilder(
|
|
71
|
+
(0, import_utils.accountSeedFromStr)(opts.accountSeed) ?? new Uint8Array(32).fill(0)
|
|
72
|
+
);
|
|
73
|
+
let accountStorageMode;
|
|
74
|
+
if (opts.storageMode === "public") {
|
|
75
|
+
accountStorageMode = AccountStorageMode.public();
|
|
76
|
+
} else if (opts.storageMode === "private") {
|
|
77
|
+
accountStorageMode = AccountStorageMode.private();
|
|
78
|
+
} else {
|
|
79
|
+
accountStorageMode = AccountStorageMode.network();
|
|
80
|
+
}
|
|
81
|
+
const account = accountBuilder.withAuthComponent(
|
|
82
|
+
AccountComponent.createAuthComponentFromCommitment(pkc, 1)
|
|
83
|
+
).accountType(opts.type).storageMode(accountStorageMode).withBasicWalletComponent().build().account;
|
|
84
|
+
if (opts.storageMode !== "private") {
|
|
85
|
+
try {
|
|
86
|
+
await midenClient.importAccountById(account.id());
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const existing = await midenClient.getAccount(account.id());
|
|
91
|
+
if (!existing) {
|
|
92
|
+
await midenClient.newAccount(account, false);
|
|
93
|
+
}
|
|
94
|
+
await midenClient.syncState();
|
|
95
|
+
return account.id().toString();
|
|
96
|
+
}
|
|
97
|
+
async function createParaMidenClient(para, wallets, opts, showSigningModal = true, customSignConfirmStep) {
|
|
98
|
+
const evmWallets = wallets.filter((wallet2) => wallet2.type === "EVM");
|
|
99
|
+
if (!evmWallets?.length) {
|
|
100
|
+
throw new Error("No EVM wallets provided");
|
|
101
|
+
}
|
|
102
|
+
const accountKeys = await Promise.all(
|
|
103
|
+
evmWallets.map((w) => (0, import_utils.getUncompressedPublicKeyFromWallet)(para, w))
|
|
104
|
+
);
|
|
105
|
+
const selectedIndex = await (0, import_modalClient.accountSelectionModal)(accountKeys);
|
|
106
|
+
const wallet = evmWallets[selectedIndex] ?? evmWallets[0];
|
|
107
|
+
const publicKey = accountKeys[selectedIndex] ?? accountKeys[0];
|
|
108
|
+
const { WebClient } = await import("@demox-labs/miden-sdk");
|
|
109
|
+
const createClientWithExternalKeystore = WebClient.createClientWithExternalKeystore;
|
|
110
|
+
if (opts.storageMode === "private" && !opts.accountSeed) {
|
|
111
|
+
throw new Error("accountSeed is required when using private storage mode");
|
|
112
|
+
}
|
|
113
|
+
const noteTransportUrl = opts.noteTransportUrl || opts.nodeTransportUrl || "https://transport.miden.io";
|
|
114
|
+
const client = await createClientWithExternalKeystore(
|
|
115
|
+
opts.endpoint,
|
|
116
|
+
noteTransportUrl,
|
|
117
|
+
opts.seed,
|
|
118
|
+
void 0,
|
|
119
|
+
void 0,
|
|
120
|
+
signCb(para, wallet, showSigningModal, customSignConfirmStep)
|
|
121
|
+
);
|
|
122
|
+
const accountId = await createAccount(
|
|
123
|
+
client,
|
|
124
|
+
publicKey,
|
|
125
|
+
opts
|
|
126
|
+
);
|
|
127
|
+
return { client, accountId };
|
|
128
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var modalClient_exports = {};
|
|
19
|
+
__export(modalClient_exports, {
|
|
20
|
+
accountSelectionModal: () => accountSelectionModal,
|
|
21
|
+
signingModal: () => signingModal
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(modalClient_exports);
|
|
24
|
+
const createModalShell = (titleText) => {
|
|
25
|
+
const existing = document.getElementById("para-signing-modal");
|
|
26
|
+
if (existing) existing.remove();
|
|
27
|
+
const overlay = document.createElement("div");
|
|
28
|
+
overlay.id = "para-signing-modal";
|
|
29
|
+
Object.assign(overlay.style, {
|
|
30
|
+
position: "fixed",
|
|
31
|
+
inset: "0",
|
|
32
|
+
background: "rgba(0, 0, 0, 0.6)",
|
|
33
|
+
display: "flex",
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
justifyContent: "center",
|
|
36
|
+
zIndex: "9999",
|
|
37
|
+
padding: "16px",
|
|
38
|
+
boxSizing: "border-box",
|
|
39
|
+
color: "#0f172a",
|
|
40
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
41
|
+
});
|
|
42
|
+
const modal = document.createElement("div");
|
|
43
|
+
Object.assign(modal.style, {
|
|
44
|
+
background: "#f8fafc",
|
|
45
|
+
borderRadius: "12px",
|
|
46
|
+
padding: "20px",
|
|
47
|
+
maxWidth: "420px",
|
|
48
|
+
width: "100%",
|
|
49
|
+
boxShadow: "0 12px 40px rgba(15, 23, 42, 0.25)"
|
|
50
|
+
});
|
|
51
|
+
const title = document.createElement("div");
|
|
52
|
+
title.textContent = titleText;
|
|
53
|
+
Object.assign(title.style, {
|
|
54
|
+
fontSize: "16px",
|
|
55
|
+
fontWeight: "600",
|
|
56
|
+
marginBottom: "12px"
|
|
57
|
+
});
|
|
58
|
+
modal.append(title);
|
|
59
|
+
overlay.append(modal);
|
|
60
|
+
return { overlay, modal };
|
|
61
|
+
};
|
|
62
|
+
const createActionsRow = () => {
|
|
63
|
+
const actions = document.createElement("div");
|
|
64
|
+
Object.assign(actions.style, {
|
|
65
|
+
display: "flex",
|
|
66
|
+
gap: "10px",
|
|
67
|
+
justifyContent: "flex-end"
|
|
68
|
+
});
|
|
69
|
+
return actions;
|
|
70
|
+
};
|
|
71
|
+
const createPrimaryButton = (label) => {
|
|
72
|
+
const button = document.createElement("button");
|
|
73
|
+
button.textContent = label;
|
|
74
|
+
Object.assign(button.style, {
|
|
75
|
+
padding: "10px 16px",
|
|
76
|
+
borderRadius: "8px",
|
|
77
|
+
border: "none",
|
|
78
|
+
background: "#0ea5e9",
|
|
79
|
+
color: "#fff",
|
|
80
|
+
cursor: "pointer",
|
|
81
|
+
fontWeight: "600"
|
|
82
|
+
});
|
|
83
|
+
return button;
|
|
84
|
+
};
|
|
85
|
+
const createSecondaryButton = (label) => {
|
|
86
|
+
const button = document.createElement("button");
|
|
87
|
+
button.textContent = label;
|
|
88
|
+
Object.assign(button.style, {
|
|
89
|
+
padding: "10px 16px",
|
|
90
|
+
borderRadius: "8px",
|
|
91
|
+
border: "1px solid #cbd5e1",
|
|
92
|
+
background: "#fff",
|
|
93
|
+
color: "#0f172a",
|
|
94
|
+
cursor: "pointer",
|
|
95
|
+
fontWeight: "600"
|
|
96
|
+
});
|
|
97
|
+
return button;
|
|
98
|
+
};
|
|
99
|
+
const signingModal = (txSummaryJson) => {
|
|
100
|
+
if (typeof document === "undefined") return Promise.resolve(true);
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
const { overlay, modal } = createModalShell(
|
|
103
|
+
"Do you want to sign this transaction?"
|
|
104
|
+
);
|
|
105
|
+
const txDetails = document.createElement("div");
|
|
106
|
+
Object.assign(txDetails.style, {
|
|
107
|
+
fontSize: "13px",
|
|
108
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
109
|
+
background: "#e2e8f0",
|
|
110
|
+
padding: "10px",
|
|
111
|
+
borderRadius: "8px",
|
|
112
|
+
marginBottom: "16px",
|
|
113
|
+
color: "#0f172a",
|
|
114
|
+
maxHeight: "300px",
|
|
115
|
+
overflowY: "auto"
|
|
116
|
+
});
|
|
117
|
+
const inputSection = document.createElement("div");
|
|
118
|
+
inputSection.style.marginBottom = "12px";
|
|
119
|
+
const inputTitle = document.createElement("div");
|
|
120
|
+
inputTitle.textContent = "Input Notes:";
|
|
121
|
+
inputTitle.style.fontWeight = "600";
|
|
122
|
+
inputTitle.style.marginBottom = "4px";
|
|
123
|
+
inputSection.append(inputTitle);
|
|
124
|
+
if (txSummaryJson.inputNotes.length === 0) {
|
|
125
|
+
const noInputs = document.createElement("div");
|
|
126
|
+
noInputs.textContent = "None";
|
|
127
|
+
noInputs.style.color = "#64748b";
|
|
128
|
+
inputSection.append(noInputs);
|
|
129
|
+
} else {
|
|
130
|
+
txSummaryJson.inputNotes.forEach((note, idx) => {
|
|
131
|
+
const noteDiv = document.createElement("div");
|
|
132
|
+
noteDiv.style.marginBottom = "8px";
|
|
133
|
+
noteDiv.style.paddingLeft = "8px";
|
|
134
|
+
noteDiv.innerHTML = `
|
|
135
|
+
<div><strong>Note ${idx + 1}:</strong> ${note.id}</div>
|
|
136
|
+
<div style="padding-left: 8px; color: #475569;">
|
|
137
|
+
Sender: ${note.sender}<br/>
|
|
138
|
+
Assets: ${note.assets.map((a) => `${a.amount} of ${a.assetId}`).join(", ") || "None"}
|
|
139
|
+
</div>
|
|
140
|
+
`;
|
|
141
|
+
inputSection.append(noteDiv);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const outputSection = document.createElement("div");
|
|
145
|
+
const outputTitle = document.createElement("div");
|
|
146
|
+
outputTitle.textContent = "Output Notes:";
|
|
147
|
+
outputTitle.style.fontWeight = "600";
|
|
148
|
+
outputTitle.style.marginBottom = "4px";
|
|
149
|
+
outputSection.append(outputTitle);
|
|
150
|
+
if (txSummaryJson.outputNotes.length === 0) {
|
|
151
|
+
const noOutputs = document.createElement("div");
|
|
152
|
+
noOutputs.textContent = "None";
|
|
153
|
+
noOutputs.style.color = "#64748b";
|
|
154
|
+
outputSection.append(noOutputs);
|
|
155
|
+
} else {
|
|
156
|
+
txSummaryJson.outputNotes.forEach((note, idx) => {
|
|
157
|
+
const noteDiv = document.createElement("div");
|
|
158
|
+
noteDiv.style.marginBottom = "8px";
|
|
159
|
+
noteDiv.style.paddingLeft = "8px";
|
|
160
|
+
noteDiv.innerHTML = `
|
|
161
|
+
<div><strong>Note ${idx + 1}:</strong> ${note.id}</div>
|
|
162
|
+
<div style="padding-left: 8px; color: #475569;">
|
|
163
|
+
Type: ${note.noteType}<br/>
|
|
164
|
+
Assets: ${note.assets.map((a) => `${a.amount} of ${a.assetId}`).join(", ") || "None"}
|
|
165
|
+
</div>
|
|
166
|
+
`;
|
|
167
|
+
outputSection.append(noteDiv);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
txDetails.append(inputSection, outputSection);
|
|
171
|
+
const actions = createActionsRow();
|
|
172
|
+
const yesBtn = createPrimaryButton("Yes");
|
|
173
|
+
const noBtn = createSecondaryButton("No");
|
|
174
|
+
const cleanup = () => {
|
|
175
|
+
overlay.remove();
|
|
176
|
+
};
|
|
177
|
+
yesBtn.onclick = () => {
|
|
178
|
+
cleanup();
|
|
179
|
+
resolve(true);
|
|
180
|
+
};
|
|
181
|
+
noBtn.onclick = () => {
|
|
182
|
+
cleanup();
|
|
183
|
+
resolve(false);
|
|
184
|
+
};
|
|
185
|
+
actions.append(noBtn, yesBtn);
|
|
186
|
+
modal.append(txDetails, actions);
|
|
187
|
+
document.body.append(overlay);
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const accountSelectionModal = (accounts) => {
|
|
191
|
+
if (accounts.length === 1 || typeof document === "undefined") {
|
|
192
|
+
return Promise.resolve(0);
|
|
193
|
+
}
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
const { overlay, modal } = createModalShell("Choose an account to use");
|
|
196
|
+
const list = document.createElement("div");
|
|
197
|
+
Object.assign(list.style, {
|
|
198
|
+
display: "flex",
|
|
199
|
+
flexDirection: "column",
|
|
200
|
+
gap: "8px",
|
|
201
|
+
marginBottom: "16px"
|
|
202
|
+
});
|
|
203
|
+
let selectedIndex = 0;
|
|
204
|
+
const items = [];
|
|
205
|
+
const highlightSelection = () => {
|
|
206
|
+
items.forEach((item, idx) => {
|
|
207
|
+
item.style.borderColor = idx === selectedIndex ? "#0ea5e9" : "#cbd5e1";
|
|
208
|
+
item.style.background = idx === selectedIndex ? "#e0f2fe" : "#e2e8f0";
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
const okBtn = createPrimaryButton("Ok");
|
|
212
|
+
okBtn.disabled = !accounts.length;
|
|
213
|
+
accounts.forEach((account, index) => {
|
|
214
|
+
const item = document.createElement("button");
|
|
215
|
+
item.type = "button";
|
|
216
|
+
item.textContent = account;
|
|
217
|
+
Object.assign(item.style, {
|
|
218
|
+
padding: "10px",
|
|
219
|
+
borderRadius: "8px",
|
|
220
|
+
border: "1px solid #cbd5e1",
|
|
221
|
+
background: "#e2e8f0",
|
|
222
|
+
fontSize: "13px",
|
|
223
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
224
|
+
color: "#0f172a",
|
|
225
|
+
wordBreak: "break-all",
|
|
226
|
+
textAlign: "left",
|
|
227
|
+
cursor: "pointer"
|
|
228
|
+
});
|
|
229
|
+
item.onclick = () => {
|
|
230
|
+
selectedIndex = index;
|
|
231
|
+
highlightSelection();
|
|
232
|
+
okBtn.disabled = false;
|
|
233
|
+
};
|
|
234
|
+
items.push(item);
|
|
235
|
+
list.append(item);
|
|
236
|
+
});
|
|
237
|
+
if (items.length) {
|
|
238
|
+
highlightSelection();
|
|
239
|
+
}
|
|
240
|
+
const actions = createActionsRow();
|
|
241
|
+
const cleanup = () => {
|
|
242
|
+
overlay.remove();
|
|
243
|
+
};
|
|
244
|
+
okBtn.onclick = () => {
|
|
245
|
+
cleanup();
|
|
246
|
+
resolve(selectedIndex);
|
|
247
|
+
};
|
|
248
|
+
actions.append(okBtn);
|
|
249
|
+
modal.append(list, actions);
|
|
250
|
+
document.body.append(overlay);
|
|
251
|
+
});
|
|
252
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __copyProps = (to, from, except, desc) => {
|
|
6
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
7
|
+
for (let key of __getOwnPropNames(from))
|
|
8
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
9
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
10
|
+
}
|
|
11
|
+
return to;
|
|
12
|
+
};
|
|
13
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
14
|
+
var types_exports = {};
|
|
15
|
+
module.exports = __toCommonJS(types_exports);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var utils_exports = {};
|
|
29
|
+
__export(utils_exports, {
|
|
30
|
+
accountSeedFromStr: () => accountSeedFromStr,
|
|
31
|
+
evmPkToCommitment: () => evmPkToCommitment,
|
|
32
|
+
fromHexSig: () => fromHexSig,
|
|
33
|
+
getUncompressedPublicKeyFromWallet: () => getUncompressedPublicKeyFromWallet,
|
|
34
|
+
hexToBytes: () => import_utils.hexToBytes,
|
|
35
|
+
txSummaryToJosn: () => txSummaryToJosn
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(utils_exports);
|
|
38
|
+
var import_utils = require("@noble/hashes/utils.js");
|
|
39
|
+
const fromHexSig = (hexString) => {
|
|
40
|
+
if (hexString.length % 2 !== 0) {
|
|
41
|
+
throw new Error("Invalid string len");
|
|
42
|
+
}
|
|
43
|
+
const sigBytes = (0, import_utils.hexToBytes)(hexString);
|
|
44
|
+
const serialized = new Uint8Array(sigBytes.length + 2);
|
|
45
|
+
serialized[0] = 1;
|
|
46
|
+
serialized.set(sigBytes, 1);
|
|
47
|
+
return serialized;
|
|
48
|
+
};
|
|
49
|
+
const accountSeedFromStr = (str) => {
|
|
50
|
+
if (!str) return;
|
|
51
|
+
const buffer = new Uint8Array(32);
|
|
52
|
+
const bytes = (0, import_utils.utf8ToBytes)(str);
|
|
53
|
+
buffer.set(bytes.slice(0, 32));
|
|
54
|
+
return buffer;
|
|
55
|
+
};
|
|
56
|
+
const evmPkToCommitment = async (uncompressedPublicKey) => {
|
|
57
|
+
const { Felt, Rpo256, FeltArray } = await import("@demox-labs/miden-sdk");
|
|
58
|
+
const withoutPrefix = uncompressedPublicKey.slice(4);
|
|
59
|
+
const x = withoutPrefix.slice(0, 64);
|
|
60
|
+
const y = withoutPrefix.slice(64);
|
|
61
|
+
const tag = parseInt(y.slice(-1), 16) % 2 === 0 ? 2 : 3;
|
|
62
|
+
const bytes = new Uint8Array(33);
|
|
63
|
+
bytes[0] = tag;
|
|
64
|
+
bytes.set((0, import_utils.hexToBytes)(x), 1);
|
|
65
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
66
|
+
const felts = Array.from(
|
|
67
|
+
{ length: 8 },
|
|
68
|
+
(_, i) => new Felt(BigInt(view.getUint32(i * 4, true)))
|
|
69
|
+
);
|
|
70
|
+
felts.push(new Felt(BigInt(bytes[32])));
|
|
71
|
+
return Rpo256.hashElements(new FeltArray(felts));
|
|
72
|
+
};
|
|
73
|
+
const getUncompressedPublicKeyFromWallet = async (para, wallet) => {
|
|
74
|
+
let publicKey = wallet.publicKey;
|
|
75
|
+
if (!publicKey) {
|
|
76
|
+
const { token } = await para.issueJwt();
|
|
77
|
+
const payload = JSON.parse(window.atob(token.split(".")[1]));
|
|
78
|
+
if (!payload.data) {
|
|
79
|
+
throw new Error("Got invalid jwt token");
|
|
80
|
+
}
|
|
81
|
+
const wallets = payload.data.connectedWallets;
|
|
82
|
+
const w = wallets.find((w2) => w2.id === wallet.id);
|
|
83
|
+
if (!w) {
|
|
84
|
+
throw new Error("Wallet Not Found in jwt data");
|
|
85
|
+
}
|
|
86
|
+
publicKey = w.publicKey;
|
|
87
|
+
}
|
|
88
|
+
return publicKey;
|
|
89
|
+
};
|
|
90
|
+
const txSummaryToJosn = (txSummary) => {
|
|
91
|
+
const inputNotes = txSummary.inputNotes().notes().map((inputNote) => ({
|
|
92
|
+
id: inputNote.id().toString(),
|
|
93
|
+
assets: inputNote.note().assets().fungibleAssets().map((asset) => {
|
|
94
|
+
return {
|
|
95
|
+
assetId: asset.faucetId().toString(),
|
|
96
|
+
amount: asset.amount().toString()
|
|
97
|
+
};
|
|
98
|
+
}),
|
|
99
|
+
sender: inputNote.note().metadata().sender().toString()
|
|
100
|
+
}));
|
|
101
|
+
const outputNotes = txSummary.outputNotes().notes().map((outputNote) => ({
|
|
102
|
+
id: outputNote.id().toString(),
|
|
103
|
+
assets: outputNote.assets().fungibleAssets().map((asset) => {
|
|
104
|
+
return {
|
|
105
|
+
assetId: asset.faucetId().toString(),
|
|
106
|
+
amount: asset.amount().toString()
|
|
107
|
+
};
|
|
108
|
+
}),
|
|
109
|
+
noteType: noteTypeToString(outputNote.metadata().noteType())
|
|
110
|
+
}));
|
|
111
|
+
return {
|
|
112
|
+
inputNotes,
|
|
113
|
+
outputNotes
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
function noteTypeToString(noteType) {
|
|
117
|
+
switch (noteType) {
|
|
118
|
+
case 1:
|
|
119
|
+
return "public";
|
|
120
|
+
case 2:
|
|
121
|
+
return "private";
|
|
122
|
+
case 3:
|
|
123
|
+
return "encrypted";
|
|
124
|
+
default:
|
|
125
|
+
return "UNKNOWN";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./midenClient.js";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hexStringToBase64
|
|
3
|
+
} from "@getpara/web-sdk";
|
|
4
|
+
import { keccak_256 as keccak256 } from "@noble/hashes/sha3.js";
|
|
5
|
+
import {
|
|
6
|
+
accountSeedFromStr,
|
|
7
|
+
evmPkToCommitment,
|
|
8
|
+
fromHexSig,
|
|
9
|
+
getUncompressedPublicKeyFromWallet,
|
|
10
|
+
txSummaryToJosn
|
|
11
|
+
} from "./utils.js";
|
|
12
|
+
import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
|
|
13
|
+
import { accountSelectionModal, signingModal } from "./modalClient.js";
|
|
14
|
+
const signCb = (para, wallet, showSigningModal, customSignConfirmStep) => {
|
|
15
|
+
return async (_, signingInputs) => {
|
|
16
|
+
const { SigningInputs } = await import("@demox-labs/miden-sdk");
|
|
17
|
+
const inputs = SigningInputs.deserialize(signingInputs);
|
|
18
|
+
let commitment = inputs.toCommitment().toHex().slice(2);
|
|
19
|
+
const hashed = bytesToHex(keccak256(hexToBytes(commitment)));
|
|
20
|
+
const txSummaryJson = txSummaryToJosn(inputs.transactionSummaryPayload());
|
|
21
|
+
if (showSigningModal) {
|
|
22
|
+
const confirmed = await signingModal(txSummaryJson);
|
|
23
|
+
if (!confirmed) {
|
|
24
|
+
throw new Error("User cancelled signing");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (customSignConfirmStep) {
|
|
28
|
+
await customSignConfirmStep(txSummaryJson);
|
|
29
|
+
}
|
|
30
|
+
console.time("Para Signing Time");
|
|
31
|
+
const res = await para.signMessage({
|
|
32
|
+
walletId: wallet.id,
|
|
33
|
+
messageBase64: hexStringToBase64(hashed)
|
|
34
|
+
});
|
|
35
|
+
console.timeEnd("Para Signing Time");
|
|
36
|
+
const signature = res.signature;
|
|
37
|
+
const sig = fromHexSig(signature);
|
|
38
|
+
return sig;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
async function createAccount(midenClient, publicKey, opts) {
|
|
42
|
+
const { AccountBuilder, AccountComponent, AccountStorageMode } = await import("@demox-labs/miden-sdk");
|
|
43
|
+
await midenClient.syncState();
|
|
44
|
+
let pkc = await evmPkToCommitment(publicKey);
|
|
45
|
+
const accountBuilder = new AccountBuilder(
|
|
46
|
+
accountSeedFromStr(opts.accountSeed) ?? new Uint8Array(32).fill(0)
|
|
47
|
+
);
|
|
48
|
+
let accountStorageMode;
|
|
49
|
+
if (opts.storageMode === "public") {
|
|
50
|
+
accountStorageMode = AccountStorageMode.public();
|
|
51
|
+
} else if (opts.storageMode === "private") {
|
|
52
|
+
accountStorageMode = AccountStorageMode.private();
|
|
53
|
+
} else {
|
|
54
|
+
accountStorageMode = AccountStorageMode.network();
|
|
55
|
+
}
|
|
56
|
+
const account = accountBuilder.withAuthComponent(
|
|
57
|
+
AccountComponent.createAuthComponentFromCommitment(pkc, 1)
|
|
58
|
+
).accountType(opts.type).storageMode(accountStorageMode).withBasicWalletComponent().build().account;
|
|
59
|
+
if (opts.storageMode !== "private") {
|
|
60
|
+
try {
|
|
61
|
+
await midenClient.importAccountById(account.id());
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const existing = await midenClient.getAccount(account.id());
|
|
66
|
+
if (!existing) {
|
|
67
|
+
await midenClient.newAccount(account, false);
|
|
68
|
+
}
|
|
69
|
+
await midenClient.syncState();
|
|
70
|
+
return account.id().toString();
|
|
71
|
+
}
|
|
72
|
+
async function createParaMidenClient(para, wallets, opts, showSigningModal = true, customSignConfirmStep) {
|
|
73
|
+
const evmWallets = wallets.filter((wallet2) => wallet2.type === "EVM");
|
|
74
|
+
if (!evmWallets?.length) {
|
|
75
|
+
throw new Error("No EVM wallets provided");
|
|
76
|
+
}
|
|
77
|
+
const accountKeys = await Promise.all(
|
|
78
|
+
evmWallets.map((w) => getUncompressedPublicKeyFromWallet(para, w))
|
|
79
|
+
);
|
|
80
|
+
const selectedIndex = await accountSelectionModal(accountKeys);
|
|
81
|
+
const wallet = evmWallets[selectedIndex] ?? evmWallets[0];
|
|
82
|
+
const publicKey = accountKeys[selectedIndex] ?? accountKeys[0];
|
|
83
|
+
const { WebClient } = await import("@demox-labs/miden-sdk");
|
|
84
|
+
const createClientWithExternalKeystore = WebClient.createClientWithExternalKeystore;
|
|
85
|
+
if (opts.storageMode === "private" && !opts.accountSeed) {
|
|
86
|
+
throw new Error("accountSeed is required when using private storage mode");
|
|
87
|
+
}
|
|
88
|
+
const noteTransportUrl = opts.noteTransportUrl || opts.nodeTransportUrl || "https://transport.miden.io";
|
|
89
|
+
const client = await createClientWithExternalKeystore(
|
|
90
|
+
opts.endpoint,
|
|
91
|
+
noteTransportUrl,
|
|
92
|
+
opts.seed,
|
|
93
|
+
void 0,
|
|
94
|
+
void 0,
|
|
95
|
+
signCb(para, wallet, showSigningModal, customSignConfirmStep)
|
|
96
|
+
);
|
|
97
|
+
const accountId = await createAccount(
|
|
98
|
+
client,
|
|
99
|
+
publicKey,
|
|
100
|
+
opts
|
|
101
|
+
);
|
|
102
|
+
return { client, accountId };
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
createParaMidenClient,
|
|
106
|
+
signCb
|
|
107
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
const createModalShell = (titleText) => {
|
|
2
|
+
const existing = document.getElementById("para-signing-modal");
|
|
3
|
+
if (existing) existing.remove();
|
|
4
|
+
const overlay = document.createElement("div");
|
|
5
|
+
overlay.id = "para-signing-modal";
|
|
6
|
+
Object.assign(overlay.style, {
|
|
7
|
+
position: "fixed",
|
|
8
|
+
inset: "0",
|
|
9
|
+
background: "rgba(0, 0, 0, 0.6)",
|
|
10
|
+
display: "flex",
|
|
11
|
+
alignItems: "center",
|
|
12
|
+
justifyContent: "center",
|
|
13
|
+
zIndex: "9999",
|
|
14
|
+
padding: "16px",
|
|
15
|
+
boxSizing: "border-box",
|
|
16
|
+
color: "#0f172a",
|
|
17
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
18
|
+
});
|
|
19
|
+
const modal = document.createElement("div");
|
|
20
|
+
Object.assign(modal.style, {
|
|
21
|
+
background: "#f8fafc",
|
|
22
|
+
borderRadius: "12px",
|
|
23
|
+
padding: "20px",
|
|
24
|
+
maxWidth: "420px",
|
|
25
|
+
width: "100%",
|
|
26
|
+
boxShadow: "0 12px 40px rgba(15, 23, 42, 0.25)"
|
|
27
|
+
});
|
|
28
|
+
const title = document.createElement("div");
|
|
29
|
+
title.textContent = titleText;
|
|
30
|
+
Object.assign(title.style, {
|
|
31
|
+
fontSize: "16px",
|
|
32
|
+
fontWeight: "600",
|
|
33
|
+
marginBottom: "12px"
|
|
34
|
+
});
|
|
35
|
+
modal.append(title);
|
|
36
|
+
overlay.append(modal);
|
|
37
|
+
return { overlay, modal };
|
|
38
|
+
};
|
|
39
|
+
const createActionsRow = () => {
|
|
40
|
+
const actions = document.createElement("div");
|
|
41
|
+
Object.assign(actions.style, {
|
|
42
|
+
display: "flex",
|
|
43
|
+
gap: "10px",
|
|
44
|
+
justifyContent: "flex-end"
|
|
45
|
+
});
|
|
46
|
+
return actions;
|
|
47
|
+
};
|
|
48
|
+
const createPrimaryButton = (label) => {
|
|
49
|
+
const button = document.createElement("button");
|
|
50
|
+
button.textContent = label;
|
|
51
|
+
Object.assign(button.style, {
|
|
52
|
+
padding: "10px 16px",
|
|
53
|
+
borderRadius: "8px",
|
|
54
|
+
border: "none",
|
|
55
|
+
background: "#0ea5e9",
|
|
56
|
+
color: "#fff",
|
|
57
|
+
cursor: "pointer",
|
|
58
|
+
fontWeight: "600"
|
|
59
|
+
});
|
|
60
|
+
return button;
|
|
61
|
+
};
|
|
62
|
+
const createSecondaryButton = (label) => {
|
|
63
|
+
const button = document.createElement("button");
|
|
64
|
+
button.textContent = label;
|
|
65
|
+
Object.assign(button.style, {
|
|
66
|
+
padding: "10px 16px",
|
|
67
|
+
borderRadius: "8px",
|
|
68
|
+
border: "1px solid #cbd5e1",
|
|
69
|
+
background: "#fff",
|
|
70
|
+
color: "#0f172a",
|
|
71
|
+
cursor: "pointer",
|
|
72
|
+
fontWeight: "600"
|
|
73
|
+
});
|
|
74
|
+
return button;
|
|
75
|
+
};
|
|
76
|
+
const signingModal = (txSummaryJson) => {
|
|
77
|
+
if (typeof document === "undefined") return Promise.resolve(true);
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const { overlay, modal } = createModalShell(
|
|
80
|
+
"Do you want to sign this transaction?"
|
|
81
|
+
);
|
|
82
|
+
const txDetails = document.createElement("div");
|
|
83
|
+
Object.assign(txDetails.style, {
|
|
84
|
+
fontSize: "13px",
|
|
85
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
86
|
+
background: "#e2e8f0",
|
|
87
|
+
padding: "10px",
|
|
88
|
+
borderRadius: "8px",
|
|
89
|
+
marginBottom: "16px",
|
|
90
|
+
color: "#0f172a",
|
|
91
|
+
maxHeight: "300px",
|
|
92
|
+
overflowY: "auto"
|
|
93
|
+
});
|
|
94
|
+
const inputSection = document.createElement("div");
|
|
95
|
+
inputSection.style.marginBottom = "12px";
|
|
96
|
+
const inputTitle = document.createElement("div");
|
|
97
|
+
inputTitle.textContent = "Input Notes:";
|
|
98
|
+
inputTitle.style.fontWeight = "600";
|
|
99
|
+
inputTitle.style.marginBottom = "4px";
|
|
100
|
+
inputSection.append(inputTitle);
|
|
101
|
+
if (txSummaryJson.inputNotes.length === 0) {
|
|
102
|
+
const noInputs = document.createElement("div");
|
|
103
|
+
noInputs.textContent = "None";
|
|
104
|
+
noInputs.style.color = "#64748b";
|
|
105
|
+
inputSection.append(noInputs);
|
|
106
|
+
} else {
|
|
107
|
+
txSummaryJson.inputNotes.forEach((note, idx) => {
|
|
108
|
+
const noteDiv = document.createElement("div");
|
|
109
|
+
noteDiv.style.marginBottom = "8px";
|
|
110
|
+
noteDiv.style.paddingLeft = "8px";
|
|
111
|
+
noteDiv.innerHTML = `
|
|
112
|
+
<div><strong>Note ${idx + 1}:</strong> ${note.id}</div>
|
|
113
|
+
<div style="padding-left: 8px; color: #475569;">
|
|
114
|
+
Sender: ${note.sender}<br/>
|
|
115
|
+
Assets: ${note.assets.map((a) => `${a.amount} of ${a.assetId}`).join(", ") || "None"}
|
|
116
|
+
</div>
|
|
117
|
+
`;
|
|
118
|
+
inputSection.append(noteDiv);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const outputSection = document.createElement("div");
|
|
122
|
+
const outputTitle = document.createElement("div");
|
|
123
|
+
outputTitle.textContent = "Output Notes:";
|
|
124
|
+
outputTitle.style.fontWeight = "600";
|
|
125
|
+
outputTitle.style.marginBottom = "4px";
|
|
126
|
+
outputSection.append(outputTitle);
|
|
127
|
+
if (txSummaryJson.outputNotes.length === 0) {
|
|
128
|
+
const noOutputs = document.createElement("div");
|
|
129
|
+
noOutputs.textContent = "None";
|
|
130
|
+
noOutputs.style.color = "#64748b";
|
|
131
|
+
outputSection.append(noOutputs);
|
|
132
|
+
} else {
|
|
133
|
+
txSummaryJson.outputNotes.forEach((note, idx) => {
|
|
134
|
+
const noteDiv = document.createElement("div");
|
|
135
|
+
noteDiv.style.marginBottom = "8px";
|
|
136
|
+
noteDiv.style.paddingLeft = "8px";
|
|
137
|
+
noteDiv.innerHTML = `
|
|
138
|
+
<div><strong>Note ${idx + 1}:</strong> ${note.id}</div>
|
|
139
|
+
<div style="padding-left: 8px; color: #475569;">
|
|
140
|
+
Type: ${note.noteType}<br/>
|
|
141
|
+
Assets: ${note.assets.map((a) => `${a.amount} of ${a.assetId}`).join(", ") || "None"}
|
|
142
|
+
</div>
|
|
143
|
+
`;
|
|
144
|
+
outputSection.append(noteDiv);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
txDetails.append(inputSection, outputSection);
|
|
148
|
+
const actions = createActionsRow();
|
|
149
|
+
const yesBtn = createPrimaryButton("Yes");
|
|
150
|
+
const noBtn = createSecondaryButton("No");
|
|
151
|
+
const cleanup = () => {
|
|
152
|
+
overlay.remove();
|
|
153
|
+
};
|
|
154
|
+
yesBtn.onclick = () => {
|
|
155
|
+
cleanup();
|
|
156
|
+
resolve(true);
|
|
157
|
+
};
|
|
158
|
+
noBtn.onclick = () => {
|
|
159
|
+
cleanup();
|
|
160
|
+
resolve(false);
|
|
161
|
+
};
|
|
162
|
+
actions.append(noBtn, yesBtn);
|
|
163
|
+
modal.append(txDetails, actions);
|
|
164
|
+
document.body.append(overlay);
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
const accountSelectionModal = (accounts) => {
|
|
168
|
+
if (accounts.length === 1 || typeof document === "undefined") {
|
|
169
|
+
return Promise.resolve(0);
|
|
170
|
+
}
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
const { overlay, modal } = createModalShell("Choose an account to use");
|
|
173
|
+
const list = document.createElement("div");
|
|
174
|
+
Object.assign(list.style, {
|
|
175
|
+
display: "flex",
|
|
176
|
+
flexDirection: "column",
|
|
177
|
+
gap: "8px",
|
|
178
|
+
marginBottom: "16px"
|
|
179
|
+
});
|
|
180
|
+
let selectedIndex = 0;
|
|
181
|
+
const items = [];
|
|
182
|
+
const highlightSelection = () => {
|
|
183
|
+
items.forEach((item, idx) => {
|
|
184
|
+
item.style.borderColor = idx === selectedIndex ? "#0ea5e9" : "#cbd5e1";
|
|
185
|
+
item.style.background = idx === selectedIndex ? "#e0f2fe" : "#e2e8f0";
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
const okBtn = createPrimaryButton("Ok");
|
|
189
|
+
okBtn.disabled = !accounts.length;
|
|
190
|
+
accounts.forEach((account, index) => {
|
|
191
|
+
const item = document.createElement("button");
|
|
192
|
+
item.type = "button";
|
|
193
|
+
item.textContent = account;
|
|
194
|
+
Object.assign(item.style, {
|
|
195
|
+
padding: "10px",
|
|
196
|
+
borderRadius: "8px",
|
|
197
|
+
border: "1px solid #cbd5e1",
|
|
198
|
+
background: "#e2e8f0",
|
|
199
|
+
fontSize: "13px",
|
|
200
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
201
|
+
color: "#0f172a",
|
|
202
|
+
wordBreak: "break-all",
|
|
203
|
+
textAlign: "left",
|
|
204
|
+
cursor: "pointer"
|
|
205
|
+
});
|
|
206
|
+
item.onclick = () => {
|
|
207
|
+
selectedIndex = index;
|
|
208
|
+
highlightSelection();
|
|
209
|
+
okBtn.disabled = false;
|
|
210
|
+
};
|
|
211
|
+
items.push(item);
|
|
212
|
+
list.append(item);
|
|
213
|
+
});
|
|
214
|
+
if (items.length) {
|
|
215
|
+
highlightSelection();
|
|
216
|
+
}
|
|
217
|
+
const actions = createActionsRow();
|
|
218
|
+
const cleanup = () => {
|
|
219
|
+
overlay.remove();
|
|
220
|
+
};
|
|
221
|
+
okBtn.onclick = () => {
|
|
222
|
+
cleanup();
|
|
223
|
+
resolve(selectedIndex);
|
|
224
|
+
};
|
|
225
|
+
actions.append(okBtn);
|
|
226
|
+
modal.append(list, actions);
|
|
227
|
+
document.body.append(overlay);
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
export {
|
|
231
|
+
accountSelectionModal,
|
|
232
|
+
signingModal
|
|
233
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { hexToBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
2
|
+
const fromHexSig = (hexString) => {
|
|
3
|
+
if (hexString.length % 2 !== 0) {
|
|
4
|
+
throw new Error("Invalid string len");
|
|
5
|
+
}
|
|
6
|
+
const sigBytes = hexToBytes(hexString);
|
|
7
|
+
const serialized = new Uint8Array(sigBytes.length + 2);
|
|
8
|
+
serialized[0] = 1;
|
|
9
|
+
serialized.set(sigBytes, 1);
|
|
10
|
+
return serialized;
|
|
11
|
+
};
|
|
12
|
+
const accountSeedFromStr = (str) => {
|
|
13
|
+
if (!str) return;
|
|
14
|
+
const buffer = new Uint8Array(32);
|
|
15
|
+
const bytes = utf8ToBytes(str);
|
|
16
|
+
buffer.set(bytes.slice(0, 32));
|
|
17
|
+
return buffer;
|
|
18
|
+
};
|
|
19
|
+
const evmPkToCommitment = async (uncompressedPublicKey) => {
|
|
20
|
+
const { Felt, Rpo256, FeltArray } = await import("@demox-labs/miden-sdk");
|
|
21
|
+
const withoutPrefix = uncompressedPublicKey.slice(4);
|
|
22
|
+
const x = withoutPrefix.slice(0, 64);
|
|
23
|
+
const y = withoutPrefix.slice(64);
|
|
24
|
+
const tag = parseInt(y.slice(-1), 16) % 2 === 0 ? 2 : 3;
|
|
25
|
+
const bytes = new Uint8Array(33);
|
|
26
|
+
bytes[0] = tag;
|
|
27
|
+
bytes.set(hexToBytes(x), 1);
|
|
28
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
29
|
+
const felts = Array.from(
|
|
30
|
+
{ length: 8 },
|
|
31
|
+
(_, i) => new Felt(BigInt(view.getUint32(i * 4, true)))
|
|
32
|
+
);
|
|
33
|
+
felts.push(new Felt(BigInt(bytes[32])));
|
|
34
|
+
return Rpo256.hashElements(new FeltArray(felts));
|
|
35
|
+
};
|
|
36
|
+
const getUncompressedPublicKeyFromWallet = async (para, wallet) => {
|
|
37
|
+
let publicKey = wallet.publicKey;
|
|
38
|
+
if (!publicKey) {
|
|
39
|
+
const { token } = await para.issueJwt();
|
|
40
|
+
const payload = JSON.parse(window.atob(token.split(".")[1]));
|
|
41
|
+
if (!payload.data) {
|
|
42
|
+
throw new Error("Got invalid jwt token");
|
|
43
|
+
}
|
|
44
|
+
const wallets = payload.data.connectedWallets;
|
|
45
|
+
const w = wallets.find((w2) => w2.id === wallet.id);
|
|
46
|
+
if (!w) {
|
|
47
|
+
throw new Error("Wallet Not Found in jwt data");
|
|
48
|
+
}
|
|
49
|
+
publicKey = w.publicKey;
|
|
50
|
+
}
|
|
51
|
+
return publicKey;
|
|
52
|
+
};
|
|
53
|
+
const txSummaryToJosn = (txSummary) => {
|
|
54
|
+
const inputNotes = txSummary.inputNotes().notes().map((inputNote) => ({
|
|
55
|
+
id: inputNote.id().toString(),
|
|
56
|
+
assets: inputNote.note().assets().fungibleAssets().map((asset) => {
|
|
57
|
+
return {
|
|
58
|
+
assetId: asset.faucetId().toString(),
|
|
59
|
+
amount: asset.amount().toString()
|
|
60
|
+
};
|
|
61
|
+
}),
|
|
62
|
+
sender: inputNote.note().metadata().sender().toString()
|
|
63
|
+
}));
|
|
64
|
+
const outputNotes = txSummary.outputNotes().notes().map((outputNote) => ({
|
|
65
|
+
id: outputNote.id().toString(),
|
|
66
|
+
assets: outputNote.assets().fungibleAssets().map((asset) => {
|
|
67
|
+
return {
|
|
68
|
+
assetId: asset.faucetId().toString(),
|
|
69
|
+
amount: asset.amount().toString()
|
|
70
|
+
};
|
|
71
|
+
}),
|
|
72
|
+
noteType: noteTypeToString(outputNote.metadata().noteType())
|
|
73
|
+
}));
|
|
74
|
+
return {
|
|
75
|
+
inputNotes,
|
|
76
|
+
outputNotes
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
function noteTypeToString(noteType) {
|
|
80
|
+
switch (noteType) {
|
|
81
|
+
case 1:
|
|
82
|
+
return "public";
|
|
83
|
+
case 2:
|
|
84
|
+
return "private";
|
|
85
|
+
case 3:
|
|
86
|
+
return "encrypted";
|
|
87
|
+
default:
|
|
88
|
+
return "UNKNOWN";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
accountSeedFromStr,
|
|
93
|
+
evmPkToCommitment,
|
|
94
|
+
fromHexSig,
|
|
95
|
+
getUncompressedPublicKeyFromWallet,
|
|
96
|
+
hexToBytes,
|
|
97
|
+
txSummaryToJosn
|
|
98
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ParaWeb, Wallet } from '@getpara/web-sdk';
|
|
2
|
+
import type { Opts, TxSummaryJson } from './types.js';
|
|
3
|
+
export type CustomSignConfirmStep = (txSummaryJson: TxSummaryJson) => Promise<unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a signing callback that routes Miden signing requests through Para.
|
|
6
|
+
* Prompts the user with a modal before delegating the keccak-hashed message to Para's signer,
|
|
7
|
+
* and optionally runs a custom confirmation step in between.
|
|
8
|
+
*/
|
|
9
|
+
export declare const signCb: (para: ParaWeb, wallet: Wallet, showSigningModal: boolean, customSignConfirmStep?: CustomSignConfirmStep) => (_: Uint8Array, signingInputs: Uint8Array) => Promise<Uint8Array<ArrayBuffer>>;
|
|
10
|
+
/**
|
|
11
|
+
* Builds a Miden WebClient wired to Para wallets and ensures an account exists for the user.
|
|
12
|
+
* Filters to EVM wallets, prompts for selection, creates the external keystore client, and
|
|
13
|
+
* hydrates or creates the corresponding Miden account before returning the client + account id.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createParaMidenClient(para: ParaWeb, wallets: Wallet[], opts: Opts, showSigningModal?: boolean, customSignConfirmStep?: CustomSignConfirmStep): Promise<{
|
|
16
|
+
client: any;
|
|
17
|
+
accountId: string;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TxSummaryJson } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Shows a lightweight confirmation modal for signing a hashed transaction.
|
|
4
|
+
* Resolves to true when the user confirms, false when cancelled; no-op on the server.
|
|
5
|
+
*/
|
|
6
|
+
export declare const signingModal: (txSummaryJson: TxSummaryJson) => Promise<boolean>;
|
|
7
|
+
/**
|
|
8
|
+
* Shows a selectable list of account commitments and resolves with the chosen index.
|
|
9
|
+
* Returns 0 immediately when only one account is provided or when running server-side.
|
|
10
|
+
*/
|
|
11
|
+
export declare const accountSelectionModal: (accounts: string[]) => Promise<number>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface MidenClientOpts {
|
|
2
|
+
endpoint?: string;
|
|
3
|
+
noteTransportUrl?: string;
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Use noteTransportUrl instead
|
|
6
|
+
*/
|
|
7
|
+
nodeTransportUrl?: string;
|
|
8
|
+
seed?: string;
|
|
9
|
+
}
|
|
10
|
+
export type MidenAccountStorageMode = 'public' | 'private' | 'network';
|
|
11
|
+
export interface MidenAccountOpts {
|
|
12
|
+
accountSeed?: string;
|
|
13
|
+
type: import('@demox-labs/miden-sdk').AccountType;
|
|
14
|
+
storageMode: MidenAccountStorageMode;
|
|
15
|
+
}
|
|
16
|
+
export type Opts = MidenClientOpts & MidenAccountOpts;
|
|
17
|
+
interface NoteIdAndAsset {
|
|
18
|
+
id: string;
|
|
19
|
+
assets: {
|
|
20
|
+
assetId: string;
|
|
21
|
+
amount: string;
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
interface InputNoteSender {
|
|
25
|
+
sender: string;
|
|
26
|
+
}
|
|
27
|
+
interface OutputNoteType {
|
|
28
|
+
noteType: string;
|
|
29
|
+
}
|
|
30
|
+
export interface TxSummaryJson {
|
|
31
|
+
inputNotes: (NoteIdAndAsset & InputNoteSender)[];
|
|
32
|
+
outputNotes: (NoteIdAndAsset & OutputNoteType)[];
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import ParaWeb, { Wallet } from '@getpara/web-sdk';
|
|
2
|
+
import { hexToBytes } from '@noble/hashes/utils.js';
|
|
3
|
+
import { TxSummaryJson } from './types';
|
|
4
|
+
export { hexToBytes };
|
|
5
|
+
/**
|
|
6
|
+
* Converts a Para hex signature into the serialized format expected by Miden.
|
|
7
|
+
* Prefixes the auth scheme byte and appends the extra padding byte required by current crypto libs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const fromHexSig: (hexString: string) => Uint8Array<ArrayBuffer>;
|
|
10
|
+
/**
|
|
11
|
+
* Derives a 32-byte seed buffer from a UTF-8 string, truncating when longer than 32 bytes.
|
|
12
|
+
*/
|
|
13
|
+
export declare const accountSeedFromStr: (str?: string) => Uint8Array<ArrayBuffer>;
|
|
14
|
+
/**
|
|
15
|
+
* Converts an uncompressed EVM public key into a Miden commitment (RPO hash of tagged X coord).
|
|
16
|
+
* Assumes input format `0x04${x}${y}` where x and y are 64-char hex strings.
|
|
17
|
+
*/
|
|
18
|
+
export declare const evmPkToCommitment: (uncompressedPublicKey: string) => Promise<import("@demox-labs/miden-sdk").Word>;
|
|
19
|
+
/**
|
|
20
|
+
* Retrieves the uncompressed public key for a Para wallet, falling back to JWT data when absent.
|
|
21
|
+
*/
|
|
22
|
+
export declare const getUncompressedPublicKeyFromWallet: (para: ParaWeb, wallet: Wallet) => Promise<string>;
|
|
23
|
+
export declare const txSummaryToJosn: (txSummary: import("@demox-labs/miden-sdk").TransactionSummary) => TxSummaryJson;
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@miden-sdk/miden-para",
|
|
3
|
+
"version": "0.10.10",
|
|
4
|
+
"description": "Miden x Para Integration",
|
|
5
|
+
"packageManager": "yarn@1.22.22",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Miden Labs",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/0xMiden/miden-para.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://miden.xyz/",
|
|
13
|
+
"readmeFilename": "README.md",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/0xMiden/miden-para/issues"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@noble/hashes": "^2.0.1"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@demox-labs/miden-sdk": "^0.12.5",
|
|
22
|
+
"@getpara/web-sdk": "2.0.0-alpha.73",
|
|
23
|
+
"@typescript-eslint/parser": "^8.48.0",
|
|
24
|
+
"esbuild": "^0.24.0",
|
|
25
|
+
"eslint": "^9.39.1",
|
|
26
|
+
"glob": "^11.0.1",
|
|
27
|
+
"prettier": "^3.7.3",
|
|
28
|
+
"puppeteer": "^24.4.0",
|
|
29
|
+
"react": "^19.2.0",
|
|
30
|
+
"react-test-renderer": "^19.2.0",
|
|
31
|
+
"typescript": "^5.8.3"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@demox-labs/miden-sdk": "^0.12.5",
|
|
35
|
+
"@getpara/web-sdk": "2.0.0-alpha.73"
|
|
36
|
+
},
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/types/index.d.ts",
|
|
40
|
+
"import": "./dist/esm/index.js",
|
|
41
|
+
"require": "./dist/cjs/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"package.json",
|
|
47
|
+
"docs/INTEGRATIONS.md"
|
|
48
|
+
],
|
|
49
|
+
"main": "dist/cjs/index.js",
|
|
50
|
+
"module": "dist/esm/index.js",
|
|
51
|
+
"scripts": {
|
|
52
|
+
"test": "node --test tests",
|
|
53
|
+
"build": "rm -rf dist && node ./scripts/build.mjs && npm run build:types",
|
|
54
|
+
"build:cjs": "rm -rf dist/cjs && tsc --module commonjs --outDir dist/cjs && printf '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
|
|
55
|
+
"build:esm": "rm -rf dist/esm && tsc --module es6 --outDir dist/esm && printf '{\"type\":\"module\",\"sideEffects\":false}' > dist/esm/package.json",
|
|
56
|
+
"build:types": "rm -rf dist/types && tsc --module es2020 --declarationDir dist/types --emitDeclarationOnly --declaration",
|
|
57
|
+
"format": "yarn prettier \"**/*.{js,jsx,ts,tsx}\" --write && yarn eslint . --fix",
|
|
58
|
+
"format-check": "yarn prettier \"**/*.{js,jsx,ts,tsx}\" && yarn eslint",
|
|
59
|
+
"old-build": "yarn build:cjs && yarn build:esm && yarn build:types",
|
|
60
|
+
"prepack": "npm run build && node -e \"require('fs').mkdirSync('build', { recursive: true });\"",
|
|
61
|
+
"postpack": "node ./scripts/postpack.mjs",
|
|
62
|
+
"publish": "npm publish --access public"
|
|
63
|
+
},
|
|
64
|
+
"sideEffects": false,
|
|
65
|
+
"types": "dist/types/index.d.ts",
|
|
66
|
+
"typings": "dist/types/index.d.ts",
|
|
67
|
+
"keywords": [
|
|
68
|
+
"miden",
|
|
69
|
+
"para",
|
|
70
|
+
"zk",
|
|
71
|
+
"wallet"
|
|
72
|
+
],
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public"
|
|
75
|
+
}
|
|
76
|
+
}
|