@moon-x/core 0.4.0 → 0.5.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.
Files changed (38) hide show
  1. package/dist/{chunk-6LTEG7VN.mjs → chunk-I56GRKAG.mjs} +19 -0
  2. package/dist/{chunk-VVL65GIB.mjs → chunk-JU5MWLXH.mjs} +14 -0
  3. package/dist/index.d.mts +2 -3
  4. package/dist/index.d.ts +2 -3
  5. package/dist/index.js +14 -0
  6. package/dist/index.mjs +1 -1
  7. package/dist/{interfaces-pNTEOKaK.d.ts → interfaces-1vfik3Ef.d.ts} +1 -1
  8. package/dist/{interfaces-cdKh3sLA.d.mts → interfaces-BdIeMGGD.d.mts} +1 -1
  9. package/dist/lib/index.d.mts +32 -2
  10. package/dist/lib/index.d.ts +32 -2
  11. package/dist/lib/index.js +21 -0
  12. package/dist/lib/index.mjs +5 -1
  13. package/dist/{post-message-CmgAfkOS.d.mts → post-message-Z6cLf0mS.d.mts} +3 -0
  14. package/dist/{post-message-CmgAfkOS.d.ts → post-message-Z6cLf0mS.d.ts} +3 -0
  15. package/dist/react/ethereum.d.mts +0 -1
  16. package/dist/react/ethereum.d.ts +0 -1
  17. package/dist/react/index.d.mts +41 -3
  18. package/dist/react/index.d.ts +41 -3
  19. package/dist/react/index.js +138 -12
  20. package/dist/react/index.mjs +137 -12
  21. package/dist/react/solana.d.mts +0 -1
  22. package/dist/react/solana.d.ts +0 -1
  23. package/dist/sdk/index.d.mts +63 -9
  24. package/dist/sdk/index.d.ts +63 -9
  25. package/dist/sdk/index.js +270 -17
  26. package/dist/sdk/index.mjs +255 -18
  27. package/dist/types/index.d.mts +135 -3
  28. package/dist/types/index.d.ts +135 -3
  29. package/dist/utils/index.d.mts +1 -1
  30. package/dist/utils/index.d.ts +1 -1
  31. package/dist/utils/index.js +14 -0
  32. package/dist/utils/index.mjs +1 -1
  33. package/dist/web/index.d.mts +2 -2
  34. package/dist/web/index.d.ts +2 -2
  35. package/dist/web/index.mjs +1 -1
  36. package/package.json +3 -1
  37. package/dist/chain-C9dvKXUY.d.mts +0 -48
  38. package/dist/chain-C9dvKXUY.d.ts +0 -48
package/dist/sdk/index.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/sdk/index.ts
31
31
  var sdk_exports = {};
32
32
  __export(sdk_exports, {
33
+ EphemeralSignerSignError: () => EphemeralSignerSignError,
33
34
  assertPasskeyInParent: () => assertPasskeyInParent,
34
35
  flattenPresenceTokens: () => flattenPresenceTokens,
35
36
  getHeadlessAuthenticationMethods: () => getHeadlessAuthenticationMethods,
@@ -37,7 +38,8 @@ __export(sdk_exports, {
37
38
  getHeadlessGeneralMethods: () => getHeadlessGeneralMethods,
38
39
  getHeadlessSolanaMethods: () => getHeadlessSolanaMethods,
39
40
  getInternalPresenceTokens: () => getInternalPresenceTokens,
40
- getPresenceToken: () => getPresenceToken
41
+ getPresenceToken: () => getPresenceToken,
42
+ signWithEphemeralSigner: () => signWithEphemeralSigner
41
43
  });
42
44
  module.exports = __toCommonJS(sdk_exports);
43
45
 
@@ -244,6 +246,20 @@ var PostMessageMethod = {
244
246
  // dispatcher boring.
245
247
  SIGN_HASH: "SIGN_HASH",
246
248
  SIGN_7702_AUTHORIZATION: "SIGN_7702_AUTHORIZATION",
249
+ // Provision a SES (Secure Ephemeral Signer) over one or more of the
250
+ // user's MPC wallets. Iframe verifies the Nitro attestation, decrypts
251
+ // the selected keyshares, HPKE-seals them to the verified enclave
252
+ // pubkey, and returns a freshly-generated Ed25519 signer keypair.
253
+ // MoonX never persists the private key — the parent must hand it to
254
+ // the user immediately.
255
+ PROVISION_EPHEMERAL_SIGNER: "PROVISION_EPHEMERAL_SIGNER",
256
+ // List the user's active provisioned ephemeral signers from the
257
+ // wallets backend. Read-only; doesn't touch SES or the enclave.
258
+ LIST_EPHEMERAL_SIGNERS: "LIST_EPHEMERAL_SIGNERS",
259
+ // Soft-revoke a previously-provisioned ephemeral signer. The wallets
260
+ // backend updates revoked_at locally and forwards the revoke to SES
261
+ // so the enclave drops the policy.
262
+ REVOKE_EPHEMERAL_SIGNER: "REVOKE_EPHEMERAL_SIGNER",
247
263
  VERIFY_EMAIL_OTP: "VERIFY_EMAIL_OTP",
248
264
  VERIFY_OAUTH: "VERIFY_OAUTH",
249
265
  START_OAUTH: "START_OAUTH",
@@ -357,7 +373,8 @@ var getInternalPresenceTokens = async ({
357
373
  passkey,
358
374
  publishableKey,
359
375
  relyingPartyOrigin,
360
- scopes
376
+ scopes,
377
+ scopeCounts
361
378
  }) => {
362
379
  const { token, optionsJSON } = await transport.send(
363
380
  PostMessageType.AUTH,
@@ -365,24 +382,36 @@ var getInternalPresenceTokens = async ({
365
382
  { publishableKey, relyingPartyOrigin }
366
383
  );
367
384
  const { response, userHandleB64url, credentialIdB64url } = await passkey.assertForInternalPresence({ optionsJSON });
385
+ const verifyPayload = {
386
+ publishableKey,
387
+ relyingPartyOrigin,
388
+ token,
389
+ response,
390
+ purpose: "internal"
391
+ };
392
+ if (scopes && scopes.length > 0) verifyPayload.scopes = scopes;
393
+ if (scopeCounts && Object.keys(scopeCounts).length > 0) {
394
+ verifyPayload.scope_counts = scopeCounts;
395
+ }
368
396
  const verify = await transport.send(
369
397
  PostMessageType.AUTH,
370
398
  PostMessageMethod.PASSKEY_PRESENCE_VERIFY,
371
- {
372
- publishableKey,
373
- relyingPartyOrigin,
374
- token,
375
- response,
376
- purpose: "internal",
377
- scopes
378
- }
399
+ verifyPayload
379
400
  );
380
401
  const out = {};
381
402
  for (const [scope, t] of Object.entries(verify.tokens ?? {})) {
382
403
  out[scope] = { token: t.token, expiresAt: t.expires_at };
383
404
  }
405
+ const outBatches = {};
406
+ for (const [scope, batch] of Object.entries(verify.token_batches ?? {})) {
407
+ outBatches[scope] = batch.map((t) => ({
408
+ token: t.token,
409
+ expiresAt: t.expires_at
410
+ }));
411
+ }
384
412
  return {
385
413
  tokens: out,
414
+ tokenBatches: outBatches,
386
415
  userHandleB64url,
387
416
  credentialIdB64url
388
417
  };
@@ -829,6 +858,8 @@ var getHeadlessAuthenticationMethods = ({
829
858
  var SCOPES_CREATE_WALLET = ["dek_bootstrap", "keyshare_write"];
830
859
  var SCOPES_IMPORT_KEY = ["dek_bootstrap", "keyshare_write"];
831
860
  var SCOPES_REMOVE_PASSKEY = ["passkey_remove"];
861
+ var SCOPES_PROVISION_EPHEMERAL_SIGNER = ["keyshare_read"];
862
+ var SCOPES_REVOKE_EPHEMERAL_SIGNER = ["ephemeral_signer_revoke"];
832
863
  var getHeadlessGeneralMethods = ({
833
864
  transport,
834
865
  passkey,
@@ -844,7 +875,11 @@ var getHeadlessGeneralMethods = ({
844
875
  { publishableKey }
845
876
  ),
846
877
  removePasskey: async (credentialIdB64url) => {
847
- const { tokens, userHandleB64url, credentialIdB64url: assertedCredId } = await getInternalPresenceTokens({
878
+ const {
879
+ tokens,
880
+ userHandleB64url,
881
+ credentialIdB64url: assertedCredId
882
+ } = await getInternalPresenceTokens({
848
883
  transport,
849
884
  passkey,
850
885
  publishableKey,
@@ -917,6 +952,83 @@ var getHeadlessGeneralMethods = ({
917
952
  );
918
953
  return { wallet };
919
954
  },
955
+ provisionEphemeralSigner: async (params) => {
956
+ const walletCount = params.walletIds.length;
957
+ if (walletCount === 0) {
958
+ throw new Error(
959
+ "provisionEphemeralSigner: at least one walletId required"
960
+ );
961
+ }
962
+ const { userHandleB64url, credentialIdB64url, tokenBatches } = await getInternalPresenceTokens({
963
+ transport,
964
+ passkey,
965
+ publishableKey,
966
+ relyingPartyOrigin,
967
+ scopeCounts: { [SCOPES_PROVISION_EPHEMERAL_SIGNER[0]]: walletCount }
968
+ });
969
+ const keyshareReadBatch = (tokenBatches[SCOPES_PROVISION_EPHEMERAL_SIGNER[0]] ?? []).map((t) => t.token);
970
+ if (keyshareReadBatch.length !== walletCount) {
971
+ throw new Error(
972
+ `provisionEphemeralSigner: server returned ${keyshareReadBatch.length} tokens, expected ${walletCount}`
973
+ );
974
+ }
975
+ return transport.send(
976
+ PostMessageType.WALLET,
977
+ PostMessageMethod.PROVISION_EPHEMERAL_SIGNER,
978
+ {
979
+ publishableKey,
980
+ userHandleB64url,
981
+ credentialIdB64url,
982
+ // One keyshare_read token per wallet, parallel to params.walletIds
983
+ // (iframe consumes them positionally after wallet resolution).
984
+ presenceTokenBatches: {
985
+ [SCOPES_PROVISION_EPHEMERAL_SIGNER[0]]: keyshareReadBatch
986
+ },
987
+ walletIds: params.walletIds,
988
+ expiresAt: params.expiresAt?.toISOString(),
989
+ label: params.label
990
+ }
991
+ );
992
+ },
993
+ // Read-only: list the user's active (non-revoked) provisioned agents
994
+ // for the current app. No WebAuthn ceremony needed — the wallet
995
+ // session JWT is enough since this exposes only metadata (api_pub,
996
+ // key_ids, expiry) and not the private agent material.
997
+ listEphemeralSigners: async () => {
998
+ return transport.send(
999
+ PostMessageType.WALLET,
1000
+ PostMessageMethod.LIST_EPHEMERAL_SIGNERS,
1001
+ { publishableKey }
1002
+ );
1003
+ },
1004
+ // Soft-revoke a signer (and forward the revoke to SES). Requires a
1005
+ // fresh WebAuthn-bound presence token: ephemeral_signer_revoke scope. Wallet JWT
1006
+ // alone isn't enough because revoke is destructive (DoS-grade) — the
1007
+ // presence proof confirms it's really the user driving it.
1008
+ revokeEphemeralSigner: async (params) => {
1009
+ const { userHandleB64url, credentialIdB64url, tokens } = await getInternalPresenceTokens({
1010
+ transport,
1011
+ passkey,
1012
+ publishableKey,
1013
+ relyingPartyOrigin,
1014
+ scopes: SCOPES_REVOKE_EPHEMERAL_SIGNER
1015
+ });
1016
+ await transport.send(
1017
+ PostMessageType.WALLET,
1018
+ PostMessageMethod.REVOKE_EPHEMERAL_SIGNER,
1019
+ {
1020
+ publishableKey,
1021
+ apiPubHex: params.apiPubHex,
1022
+ userHandleB64url,
1023
+ credentialIdB64url,
1024
+ presenceTokens: flattenPresenceTokens({
1025
+ tokens,
1026
+ userHandleB64url,
1027
+ credentialIdB64url
1028
+ })
1029
+ }
1030
+ );
1031
+ },
920
1032
  getWallets: async (walletType) => {
921
1033
  const { wallets } = await transport.send(
922
1034
  PostMessageType.WALLET,
@@ -933,11 +1045,9 @@ var getHeadlessGeneralMethods = ({
933
1045
  // silently extend session lifetime.
934
1046
  getSessionTokens: async () => {
935
1047
  try {
936
- const session = await transport.send(
937
- PostMessageType.AUTH,
938
- PostMessageMethod.GET_CURRENT_SESSION,
939
- { publishableKey }
940
- );
1048
+ const session = await transport.send(PostMessageType.AUTH, PostMessageMethod.GET_CURRENT_SESSION, {
1049
+ publishableKey
1050
+ });
941
1051
  return session ? {
942
1052
  accessToken: session.accessToken,
943
1053
  identityToken: session.identityToken,
@@ -1001,8 +1111,150 @@ var getHeadlessGeneralMethods = ({
1001
1111
  }
1002
1112
  }
1003
1113
  });
1114
+
1115
+ // src/sdk/ephemeral-signer-sign.ts
1116
+ var import_ed25519 = require("@noble/curves/ed25519");
1117
+ var import_utils = require("@noble/hashes/utils");
1118
+ function normalizeBase(url) {
1119
+ return url.replace(/\/+$/, "");
1120
+ }
1121
+ var EphemeralSignerSignError = class extends Error {
1122
+ constructor(message, status, body) {
1123
+ super(message);
1124
+ this.status = status;
1125
+ this.body = body;
1126
+ this.name = "EphemeralSignerSignError";
1127
+ }
1128
+ };
1129
+ var bindingsCache = /* @__PURE__ */ new Map();
1130
+ async function resolveBinding(input) {
1131
+ const cached = bindingsCache.get(input.apiPubHex);
1132
+ if (cached) {
1133
+ const hit2 = cached.find((b) => b.walletId === input.walletId);
1134
+ if (hit2) return hit2;
1135
+ }
1136
+ const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
1137
+ const signedBodyBytes = new TextEncoder().encode(
1138
+ JSON.stringify({ issued_at: issuedAt })
1139
+ );
1140
+ const apiPrivBytes = (0, import_utils.hexToBytes)(input.apiPrivHex);
1141
+ const agentSig = import_ed25519.ed25519.sign(signedBodyBytes, apiPrivBytes);
1142
+ const url = `${normalizeBase(input.baseUrl)}/v0/wallets/ephemeral-signers/me/wallets`;
1143
+ const fetchFn = input.fetchImpl ?? fetch;
1144
+ let response;
1145
+ try {
1146
+ response = await fetchFn(url, {
1147
+ method: "POST",
1148
+ headers: {
1149
+ "Content-Type": "application/json",
1150
+ Authorization: `PublicToken ${input.publishableKey}`
1151
+ },
1152
+ body: JSON.stringify({
1153
+ api_pub: input.apiPubHex,
1154
+ signed_body: (0, import_utils.bytesToHex)(signedBodyBytes),
1155
+ agent_signature: (0, import_utils.bytesToHex)(agentSig)
1156
+ })
1157
+ });
1158
+ } catch (e) {
1159
+ throw new EphemeralSignerSignError(
1160
+ `wallet binding fetch failed: ${e instanceof Error ? e.message : String(e)}`
1161
+ );
1162
+ }
1163
+ if (!response.ok) {
1164
+ const body = await response.text().catch(() => "");
1165
+ throw new EphemeralSignerSignError(
1166
+ `wallet binding fetch failed: ${response.status}`,
1167
+ response.status,
1168
+ body
1169
+ );
1170
+ }
1171
+ const json = await response.json();
1172
+ const bindings = json.wallets.map((w) => ({
1173
+ walletId: w.wallet_id,
1174
+ keyId: w.key_id,
1175
+ derivationPath: w.derivation_path
1176
+ }));
1177
+ bindingsCache.set(input.apiPubHex, bindings);
1178
+ const hit = bindings.find((b) => b.walletId === input.walletId);
1179
+ if (!hit) {
1180
+ throw new EphemeralSignerSignError(
1181
+ `walletId ${input.walletId} is not authorized for this agent`
1182
+ );
1183
+ }
1184
+ return hit;
1185
+ }
1186
+ async function signWithEphemeralSigner(input) {
1187
+ if (!/^[0-9a-f]{64}$/i.test(input.apiPubHex)) {
1188
+ throw new EphemeralSignerSignError(
1189
+ "apiPubHex must be 64 lowercase hex chars (32-byte Ed25519 pubkey)"
1190
+ );
1191
+ }
1192
+ if (!/^[0-9a-f]{64}$/i.test(input.apiPrivHex)) {
1193
+ throw new EphemeralSignerSignError(
1194
+ "apiPrivHex must be 64 lowercase hex chars (32-byte Ed25519 privkey)"
1195
+ );
1196
+ }
1197
+ if (!input.walletId) {
1198
+ throw new EphemeralSignerSignError("walletId is required");
1199
+ }
1200
+ if (!input.rawSigningPayloadHex || !/^[0-9a-f]+$/i.test(input.rawSigningPayloadHex)) {
1201
+ throw new EphemeralSignerSignError(
1202
+ "rawSigningPayloadHex must be a non-empty lowercase hex string"
1203
+ );
1204
+ }
1205
+ if (!input.publishableKey) {
1206
+ throw new EphemeralSignerSignError(
1207
+ "publishableKey is required \u2014 wallets backend gates /ephemeral-signers/* with RequirePublicToken"
1208
+ );
1209
+ }
1210
+ const binding = await resolveBinding(input);
1211
+ const issuedAt = (input.issuedAt ?? /* @__PURE__ */ new Date()).toISOString();
1212
+ const signedPayload = {
1213
+ raw_signing_payload: input.rawSigningPayloadHex,
1214
+ key_id: binding.keyId,
1215
+ derivation_path: binding.derivationPath,
1216
+ issued_at: issuedAt
1217
+ };
1218
+ const signedBodyBytes = new TextEncoder().encode(
1219
+ JSON.stringify(signedPayload)
1220
+ );
1221
+ const apiPrivBytes = (0, import_utils.hexToBytes)(input.apiPrivHex);
1222
+ const agentSig = import_ed25519.ed25519.sign(signedBodyBytes, apiPrivBytes);
1223
+ const requestBody = {
1224
+ api_pub: input.apiPubHex,
1225
+ signed_body: (0, import_utils.bytesToHex)(signedBodyBytes),
1226
+ agent_signature: (0, import_utils.bytesToHex)(agentSig)
1227
+ };
1228
+ const url = `${normalizeBase(input.baseUrl)}/v0/wallets/ephemeral-signers/sign`;
1229
+ const fetchFn = input.fetchImpl ?? fetch;
1230
+ let response;
1231
+ try {
1232
+ response = await fetchFn(url, {
1233
+ method: "POST",
1234
+ headers: {
1235
+ "Content-Type": "application/json",
1236
+ Authorization: `PublicToken ${input.publishableKey}`
1237
+ },
1238
+ body: JSON.stringify(requestBody)
1239
+ });
1240
+ } catch (e) {
1241
+ throw new EphemeralSignerSignError(
1242
+ `transport failure: ${e instanceof Error ? e.message : String(e)}`
1243
+ );
1244
+ }
1245
+ if (!response.ok) {
1246
+ const body = await response.text().catch(() => "");
1247
+ throw new EphemeralSignerSignError(
1248
+ `ephemeral signer sign failed: ${response.status}`,
1249
+ response.status,
1250
+ body
1251
+ );
1252
+ }
1253
+ return await response.json();
1254
+ }
1004
1255
  // Annotate the CommonJS export names for ESM import in node:
1005
1256
  0 && (module.exports = {
1257
+ EphemeralSignerSignError,
1006
1258
  assertPasskeyInParent,
1007
1259
  flattenPresenceTokens,
1008
1260
  getHeadlessAuthenticationMethods,
@@ -1010,5 +1262,6 @@ var getHeadlessGeneralMethods = ({
1010
1262
  getHeadlessGeneralMethods,
1011
1263
  getHeadlessSolanaMethods,
1012
1264
  getInternalPresenceTokens,
1013
- getPresenceToken
1265
+ getPresenceToken,
1266
+ signWithEphemeralSigner
1014
1267
  });