@kingdomchain/wallet-client 0.1.0 → 0.2.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/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
- import { toHex, fromHex } from '@cosmjs/encoding';
1
+ import { toHex, fromHex, toBase64, fromBase64 } from '@cosmjs/encoding';
2
2
  import { sha256 } from '@noble/hashes/sha256';
3
3
  import * as ed from '@noble/ed25519';
4
- import { Registry } from '@cosmjs/proto-signing';
4
+ import { Registry, DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
5
5
  import { defaultRegistryTypes, SigningStargateClient } from '@cosmjs/stargate';
6
6
  import { BinaryWriter, BinaryReader } from '@bufbuild/protobuf/wire';
7
+ import SignClient from '@walletconnect/sign-client';
8
+ import { Random } from '@cosmjs/crypto';
7
9
 
8
10
  // src/attestation.ts
9
11
  var ATTESTATION_TYPE_CITIZENSHIP_APPLICATION = "citizenship_application";
@@ -1590,6 +1592,16 @@ function isKeplrAvailable() {
1590
1592
  function isLeapAvailable() {
1591
1593
  return typeof window !== "undefined" && isWalletApiPresent(window.leap);
1592
1594
  }
1595
+ function getCosmostationKeplrApi() {
1596
+ if (typeof window === "undefined") {
1597
+ return void 0;
1598
+ }
1599
+ const api = window.cosmostation?.providers?.keplr;
1600
+ return isWalletApiPresent(api) ? api : void 0;
1601
+ }
1602
+ function isCosmostationAvailable() {
1603
+ return getCosmostationKeplrApi() != null;
1604
+ }
1593
1605
  function buildKingdomChainInfo(chainId, rpcUrl, restUrl, chainName = "Kingdom Chain") {
1594
1606
  validateChainConfig(chainId, rpcUrl, restUrl);
1595
1607
  const accPub = `${BECH32_PREFIX}pub`;
@@ -1665,6 +1677,10 @@ async function connectLeap(chainInfo) {
1665
1677
  const api = resolveWalletApi(() => window.leap, "Leap");
1666
1678
  return suggestAndConnect(api, chainInfo, "Leap");
1667
1679
  }
1680
+ async function connectCosmostation(chainInfo) {
1681
+ const api = resolveWalletApi(getCosmostationKeplrApi, "Cosmostation");
1682
+ return suggestAndConnect(api, chainInfo, "Cosmostation");
1683
+ }
1668
1684
  async function createBrowserSigningClient(rpcUrl, signer) {
1669
1685
  if (!rpcUrl.trim()) {
1670
1686
  throw new InvalidChainConfigError("rpcUrl is required");
@@ -1677,6 +1693,720 @@ async function createBrowserSigningClient(rpcUrl, signer) {
1677
1693
  throw new RpcConnectionError(rpcUrl, err);
1678
1694
  }
1679
1695
  }
1696
+ var WALLETCONNECT_COSMOS_NAMESPACE = "cosmos";
1697
+ var WALLETCONNECT_COSMOS_METHODS = [
1698
+ "cosmos_getAccounts",
1699
+ "cosmos_signDirect",
1700
+ "cosmos_signAmino"
1701
+ ];
1702
+ var WalletConnectNotInitializedError = class extends Error {
1703
+ constructor() {
1704
+ super("WalletConnect Sign Client is not initialized.");
1705
+ this.name = "WalletConnectNotInitializedError";
1706
+ }
1707
+ };
1708
+ var WalletConnectSessionError = class extends Error {
1709
+ constructor(message, cause) {
1710
+ super(message, { cause });
1711
+ this.name = "WalletConnectSessionError";
1712
+ }
1713
+ };
1714
+ var WalletConnectSigningError = class extends Error {
1715
+ constructor(message, cause) {
1716
+ super(message, { cause });
1717
+ this.name = "WalletConnectSigningError";
1718
+ }
1719
+ };
1720
+ function toWalletConnectCosmosChainId(chainId) {
1721
+ return `${WALLETCONNECT_COSMOS_NAMESPACE}:${chainId}`;
1722
+ }
1723
+ function parseWalletConnectAccount(account) {
1724
+ const parts = account.split(":");
1725
+ if (parts.length !== 3) {
1726
+ throw new WalletConnectSessionError(`Invalid WalletConnect account id: ${account}`);
1727
+ }
1728
+ const [namespace, chainId, address] = parts;
1729
+ return { namespace, chainId, address };
1730
+ }
1731
+ function buildRequiredCosmosNamespaces(chainId) {
1732
+ const wcChainId = toWalletConnectCosmosChainId(chainId);
1733
+ return {
1734
+ [WALLETCONNECT_COSMOS_NAMESPACE]: {
1735
+ methods: [...WALLETCONNECT_COSMOS_METHODS],
1736
+ chains: [wcChainId],
1737
+ events: []
1738
+ }
1739
+ };
1740
+ }
1741
+ async function initWalletConnectClient(config) {
1742
+ if (!config.projectId.trim()) {
1743
+ throw new WalletConnectSessionError("WalletConnect projectId is required");
1744
+ }
1745
+ if (!config.chainId.trim()) {
1746
+ throw new WalletConnectSessionError("chainId is required");
1747
+ }
1748
+ return SignClient.init({
1749
+ projectId: config.projectId,
1750
+ metadata: config.metadata,
1751
+ relayUrl: config.relayUrl
1752
+ });
1753
+ }
1754
+ function createWalletConnectOfflineSigner(client, session, chainId) {
1755
+ const wcChainId = toWalletConnectCosmosChainId(chainId);
1756
+ const accounts = session.namespaces[WALLETCONNECT_COSMOS_NAMESPACE]?.accounts ?? [];
1757
+ const chainAccounts = accounts.map(parseWalletConnectAccount).filter((entry) => entry.chainId === chainId);
1758
+ if (chainAccounts.length === 0) {
1759
+ throw new WalletConnectSessionError(
1760
+ `WalletConnect session has no accounts for chain ${chainId}`
1761
+ );
1762
+ }
1763
+ const primaryAddress = chainAccounts[0].address;
1764
+ return {
1765
+ getAccounts: async () => {
1766
+ try {
1767
+ const response = await client.request({
1768
+ topic: session.topic,
1769
+ chainId: wcChainId,
1770
+ request: {
1771
+ method: "cosmos_getAccounts",
1772
+ params: {}
1773
+ }
1774
+ });
1775
+ if (Array.isArray(response) && response.length > 0) {
1776
+ return response.map((account) => ({
1777
+ address: account.address,
1778
+ algo: account.algo,
1779
+ pubkey: fromBase64(account.pubkey)
1780
+ }));
1781
+ }
1782
+ } catch {
1783
+ }
1784
+ return chainAccounts.map((account) => ({
1785
+ address: account.address,
1786
+ algo: "secp256k1",
1787
+ pubkey: new Uint8Array(0)
1788
+ }));
1789
+ },
1790
+ signDirect: async (signerAddress, signDoc) => {
1791
+ if (signerAddress !== primaryAddress) {
1792
+ throw new WalletConnectSigningError(
1793
+ `WalletConnect signer address mismatch: expected ${primaryAddress}, got ${signerAddress}`
1794
+ );
1795
+ }
1796
+ try {
1797
+ const result = await client.request({
1798
+ topic: session.topic,
1799
+ chainId: wcChainId,
1800
+ request: {
1801
+ method: "cosmos_signDirect",
1802
+ params: {
1803
+ signerAddress,
1804
+ signDoc: {
1805
+ chainId: signDoc.chainId,
1806
+ accountNumber: signDoc.accountNumber.toString(),
1807
+ authInfoBytes: toBase64(signDoc.authInfoBytes),
1808
+ bodyBytes: toBase64(signDoc.bodyBytes)
1809
+ }
1810
+ }
1811
+ }
1812
+ });
1813
+ return {
1814
+ signed: {
1815
+ chainId: result.signed?.chainId ?? signDoc.chainId,
1816
+ accountNumber: BigInt(result.signed?.accountNumber ?? signDoc.accountNumber),
1817
+ authInfoBytes: result.signed?.authInfoBytes ? fromBase64(result.signed.authInfoBytes) : signDoc.authInfoBytes,
1818
+ bodyBytes: result.signed?.bodyBytes ? fromBase64(result.signed.bodyBytes) : signDoc.bodyBytes
1819
+ },
1820
+ signature: {
1821
+ pub_key: {
1822
+ type: result.signature.pub_key.type,
1823
+ value: result.signature.pub_key.value
1824
+ },
1825
+ signature: result.signature.signature
1826
+ }
1827
+ };
1828
+ } catch (err) {
1829
+ throw new WalletConnectSigningError("WalletConnect cosmos_signDirect failed.", err);
1830
+ }
1831
+ }
1832
+ };
1833
+ }
1834
+ async function connectWalletConnect(client, chainId, options) {
1835
+ const { uri, approval } = await client.connect({
1836
+ pairingTopic: options?.pairingTopic,
1837
+ requiredNamespaces: buildRequiredCosmosNamespaces(chainId)
1838
+ });
1839
+ if (!uri) {
1840
+ throw new WalletConnectSessionError("WalletConnect did not return a pairing URI");
1841
+ }
1842
+ const session = await approval();
1843
+ const signer = createWalletConnectOfflineSigner(client, session, chainId);
1844
+ const accounts = await signer.getAccounts();
1845
+ if (accounts.length === 0 || !accounts[0]?.address?.trim()) {
1846
+ throw new WalletConnectSessionError("WalletConnect session returned no usable accounts");
1847
+ }
1848
+ return { uri, session, signer };
1849
+ }
1850
+ async function disconnectWalletConnect(client, session, reason = "User disconnected") {
1851
+ await client.disconnect({
1852
+ topic: session.topic,
1853
+ reason: { code: 6e3, message: reason }
1854
+ });
1855
+ }
1856
+ async function restoreWalletConnectSession(client, chainId) {
1857
+ const sessions = client.session.getAll();
1858
+ const wcChainId = toWalletConnectCosmosChainId(chainId);
1859
+ const active = sessions.find((session) => {
1860
+ const chains = session.namespaces[WALLETCONNECT_COSMOS_NAMESPACE]?.chains ?? [];
1861
+ return chains.includes(wcChainId);
1862
+ });
1863
+ if (!active) {
1864
+ return null;
1865
+ }
1866
+ const signer = createWalletConnectOfflineSigner(client, active, chainId);
1867
+ return { session: active, signer };
1868
+ }
1869
+
1870
+ // src/embeddedWallet/types.ts
1871
+ var EMBEDDED_WALLET_VAULT_VERSION = 1;
1872
+ var EMBEDDED_WALLET_KDF_PRF = "webauthn-prf";
1873
+ var EMBEDDED_WALLET_KDF_PBKDF2 = "pbkdf2-passkey";
1874
+ var EmbeddedWalletBackupRequiredError = class extends Error {
1875
+ constructor() {
1876
+ super(
1877
+ "Encrypted backup must be downloaded and acknowledged before the first transaction."
1878
+ );
1879
+ this.name = "EmbeddedWalletBackupRequiredError";
1880
+ }
1881
+ };
1882
+ var EmbeddedWalletVaultError = class extends Error {
1883
+ constructor(message, cause) {
1884
+ super(message, { cause });
1885
+ this.name = "EmbeddedWalletVaultError";
1886
+ }
1887
+ };
1888
+ var PBKDF2_ITERATIONS = 31e4;
1889
+ var AES_GCM_IV_BYTES = 12;
1890
+ function assertKekLength(kek) {
1891
+ if (kek.length !== 32) {
1892
+ throw new EmbeddedWalletVaultError(`KEK must be 32 bytes, got ${kek.length}`);
1893
+ }
1894
+ }
1895
+ function serializePlaintext(payload) {
1896
+ const json = JSON.stringify({
1897
+ privateKey: toBase64(payload.privateKey),
1898
+ hdPath: payload.hdPath,
1899
+ createdAt: payload.createdAt
1900
+ });
1901
+ return new TextEncoder().encode(json);
1902
+ }
1903
+ function deserializePlaintext(bytes) {
1904
+ let parsed;
1905
+ try {
1906
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
1907
+ } catch (err) {
1908
+ throw new EmbeddedWalletVaultError("Failed to parse decrypted wallet payload", err);
1909
+ }
1910
+ if (typeof parsed !== "object" || parsed == null || typeof parsed.hdPath !== "string" || typeof parsed.createdAt !== "string" || typeof parsed.privateKey !== "string") {
1911
+ throw new EmbeddedWalletVaultError("Decrypted wallet payload has invalid shape");
1912
+ }
1913
+ return {
1914
+ privateKey: fromBase64(parsed.privateKey),
1915
+ hdPath: parsed.hdPath,
1916
+ createdAt: parsed.createdAt
1917
+ };
1918
+ }
1919
+ async function deriveKekFromPrf(prfOutput) {
1920
+ if (prfOutput.length < 32) {
1921
+ throw new EmbeddedWalletVaultError(`PRF output must be at least 32 bytes, got ${prfOutput.length}`);
1922
+ }
1923
+ return prfOutput.slice(0, 32);
1924
+ }
1925
+ async function deriveKekFromPbkdf2(passkeyMaterial, salt, iterations = PBKDF2_ITERATIONS) {
1926
+ if (salt.length < 16) {
1927
+ throw new EmbeddedWalletVaultError("PBKDF2 salt must be at least 16 bytes");
1928
+ }
1929
+ const keyMaterial = await crypto.subtle.importKey(
1930
+ "raw",
1931
+ new Uint8Array(passkeyMaterial),
1932
+ "PBKDF2",
1933
+ false,
1934
+ ["deriveBits"]
1935
+ );
1936
+ const derived = await crypto.subtle.deriveBits(
1937
+ { name: "PBKDF2", salt: new Uint8Array(salt), iterations, hash: "SHA-256" },
1938
+ keyMaterial,
1939
+ 256
1940
+ );
1941
+ return new Uint8Array(derived);
1942
+ }
1943
+ function createPbkdf2Salt() {
1944
+ const salt = new Uint8Array(32);
1945
+ crypto.getRandomValues(salt);
1946
+ return salt;
1947
+ }
1948
+ function createPasskeyMaterialFromAssertion(authenticatorData, clientDataJSON) {
1949
+ const combined = new Uint8Array(authenticatorData.length + clientDataJSON.length);
1950
+ combined.set(authenticatorData, 0);
1951
+ combined.set(clientDataJSON, authenticatorData.length);
1952
+ return combined;
1953
+ }
1954
+ async function importAesKey(kek) {
1955
+ assertKekLength(kek);
1956
+ const raw = new Uint8Array(kek);
1957
+ return crypto.subtle.importKey("raw", raw, "AES-GCM", false, ["encrypt", "decrypt"]);
1958
+ }
1959
+ async function encryptWalletPayload(payload, options) {
1960
+ const iv = crypto.getRandomValues(new Uint8Array(AES_GCM_IV_BYTES));
1961
+ const aesKey = await importAesKey(options.kek);
1962
+ const plaintext = serializePlaintext(payload);
1963
+ const ciphertext = await crypto.subtle.encrypt(
1964
+ { name: "AES-GCM", iv },
1965
+ aesKey,
1966
+ new Uint8Array(plaintext)
1967
+ );
1968
+ if (options.kdf === EMBEDDED_WALLET_KDF_PBKDF2 && !options.kdfSalt) {
1969
+ throw new EmbeddedWalletVaultError("PBKDF2 encryption requires kdfSalt");
1970
+ }
1971
+ return {
1972
+ version: EMBEDDED_WALLET_VAULT_VERSION,
1973
+ kdf: options.kdf,
1974
+ credentialId: toBase64(options.credentialId),
1975
+ publicAddress: options.publicAddress,
1976
+ iv: toBase64(iv),
1977
+ ciphertext: toBase64(new Uint8Array(ciphertext)),
1978
+ kdfSalt: options.kdfSalt ? toBase64(options.kdfSalt) : "",
1979
+ kdfIterations: options.kdf === EMBEDDED_WALLET_KDF_PBKDF2 ? PBKDF2_ITERATIONS : 0,
1980
+ prfSalt: options.prfSalt ? toBase64(options.prfSalt) : "",
1981
+ backupAcknowledged: options.backupAcknowledged ?? false,
1982
+ backupAcknowledgedAt: options.backupAcknowledged ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
1983
+ createdAt: payload.createdAt
1984
+ };
1985
+ }
1986
+ async function decryptWalletPayload(vault, kek) {
1987
+ if (vault.version !== EMBEDDED_WALLET_VAULT_VERSION) {
1988
+ throw new EmbeddedWalletVaultError(`Unsupported vault version: ${vault.version}`);
1989
+ }
1990
+ const aesKey = await importAesKey(kek);
1991
+ const iv = fromBase64(vault.iv);
1992
+ const ciphertext = fromBase64(vault.ciphertext);
1993
+ let plaintextBytes;
1994
+ try {
1995
+ plaintextBytes = await crypto.subtle.decrypt(
1996
+ { name: "AES-GCM", iv: new Uint8Array(iv) },
1997
+ aesKey,
1998
+ new Uint8Array(ciphertext)
1999
+ );
2000
+ } catch (err) {
2001
+ throw new EmbeddedWalletVaultError("Failed to decrypt wallet vault (wrong key or corrupted data)", err);
2002
+ }
2003
+ return deserializePlaintext(new Uint8Array(plaintextBytes));
2004
+ }
2005
+ function markVaultBackupAcknowledged(vault) {
2006
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2007
+ return {
2008
+ ...vault,
2009
+ backupAcknowledged: true,
2010
+ backupAcknowledgedAt: now
2011
+ };
2012
+ }
2013
+ function assertVaultPermitsTransaction(vault) {
2014
+ if (!vault.backupAcknowledged) {
2015
+ throw new EmbeddedWalletBackupRequiredError();
2016
+ }
2017
+ }
2018
+
2019
+ // src/embeddedWallet/prf.ts
2020
+ function detectWebAuthnPrfCapabilities() {
2021
+ if (typeof globalThis.PublicKeyCredential === "undefined") {
2022
+ return {
2023
+ webAuthnAvailable: false,
2024
+ prfLikelySupported: false,
2025
+ prfFallbackReason: "WebAuthn is not available in this environment"
2026
+ };
2027
+ }
2028
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
2029
+ const isSafari = /Safari/i.test(ua) && !/Chrome|Chromium|CriOS|FxiOS/i.test(ua);
2030
+ if (isSafari) {
2031
+ return {
2032
+ webAuthnAvailable: true,
2033
+ prfLikelySupported: false,
2034
+ prfFallbackReason: "Safari does not support WebAuthn PRF for cross-device QR flows; use PBKDF2 fallback"
2035
+ };
2036
+ }
2037
+ const getCaps = PublicKeyCredential.getClientCapabilities;
2038
+ if (typeof getCaps !== "function") {
2039
+ return {
2040
+ webAuthnAvailable: true,
2041
+ prfLikelySupported: true
2042
+ };
2043
+ }
2044
+ return {
2045
+ webAuthnAvailable: true,
2046
+ prfLikelySupported: true,
2047
+ prfFallbackReason: void 0
2048
+ };
2049
+ }
2050
+ function readPrfOutput(clientExtensionResults) {
2051
+ const prf = clientExtensionResults?.prf;
2052
+ const first = prf?.results?.first ?? (prf?.results ? Object.values(prf.results)[0] : void 0);
2053
+ if (!first || first.byteLength < 32) {
2054
+ return null;
2055
+ }
2056
+ return new Uint8Array(first).slice(0, 32);
2057
+ }
2058
+ function selectEmbeddedWalletKdf(caps, prfOutput) {
2059
+ if (caps.prfLikelySupported && prfOutput && prfOutput.length >= 32) {
2060
+ return "webauthn-prf";
2061
+ }
2062
+ return "pbkdf2-passkey";
2063
+ }
2064
+
2065
+ // src/embeddedWallet/backup.ts
2066
+ function exportEncryptedBackup(vault) {
2067
+ return {
2068
+ format: "kingdom-os-embedded-wallet-backup",
2069
+ version: EMBEDDED_WALLET_VAULT_VERSION,
2070
+ vault: structuredClone(vault),
2071
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
2072
+ };
2073
+ }
2074
+ function parseEncryptedBackup(json) {
2075
+ let parsed;
2076
+ try {
2077
+ parsed = JSON.parse(json);
2078
+ } catch (err) {
2079
+ throw new Error("Backup file is not valid JSON", { cause: err });
2080
+ }
2081
+ if (typeof parsed !== "object" || parsed == null || parsed.format !== "kingdom-os-embedded-wallet-backup" || parsed.version !== EMBEDDED_WALLET_VAULT_VERSION || typeof parsed.vault !== "object") {
2082
+ throw new Error("Unrecognized embedded wallet backup format");
2083
+ }
2084
+ return parsed;
2085
+ }
2086
+ function serializeBackupFile(backup) {
2087
+ return JSON.stringify(backup, null, 2);
2088
+ }
2089
+ var WEBAUTHN_RP_NAME = "Kingdom OS";
2090
+ var WebAuthnUnavailableError = class extends Error {
2091
+ constructor(message = "WebAuthn is not available in this browser") {
2092
+ super(message);
2093
+ this.name = "WebAuthnUnavailableError";
2094
+ }
2095
+ };
2096
+ var WebAuthnUserCancelledError = class extends Error {
2097
+ constructor(message = "Passkey request was cancelled") {
2098
+ super(message);
2099
+ this.name = "WebAuthnUserCancelledError";
2100
+ }
2101
+ };
2102
+ function randomChallenge() {
2103
+ return toBufferSource(crypto.getRandomValues(new Uint8Array(32)));
2104
+ }
2105
+ function toBufferSource(bytes) {
2106
+ return Uint8Array.from(bytes);
2107
+ }
2108
+ function resolveRpId(explicit) {
2109
+ if (explicit?.trim()) {
2110
+ return explicit.trim();
2111
+ }
2112
+ if (typeof window !== "undefined" && window.location.hostname) {
2113
+ return window.location.hostname;
2114
+ }
2115
+ throw new EmbeddedWalletVaultError("rpId is required when window.location is unavailable");
2116
+ }
2117
+ function mapWebAuthnError(err) {
2118
+ if (err instanceof DOMException) {
2119
+ if (err.name === "NotAllowedError") {
2120
+ return new WebAuthnUserCancelledError();
2121
+ }
2122
+ return new EmbeddedWalletVaultError(`WebAuthn failed: ${err.name}`, err);
2123
+ }
2124
+ return err instanceof Error ? err : new EmbeddedWalletVaultError(String(err));
2125
+ }
2126
+ async function registerPasskey(options) {
2127
+ if (typeof navigator === "undefined" || !navigator.credentials?.create) {
2128
+ throw new WebAuthnUnavailableError();
2129
+ }
2130
+ const rpId = resolveRpId(options.rpId);
2131
+ const prfSalt = options.prfSalt ?? crypto.getRandomValues(new Uint8Array(32));
2132
+ try {
2133
+ const credential = await navigator.credentials.create({
2134
+ publicKey: {
2135
+ challenge: randomChallenge(),
2136
+ rp: { name: WEBAUTHN_RP_NAME, id: rpId },
2137
+ user: {
2138
+ id: toBufferSource(options.userId),
2139
+ name: options.userName,
2140
+ displayName: options.displayName
2141
+ },
2142
+ pubKeyCredParams: [{ alg: -7, type: "public-key" }],
2143
+ authenticatorSelection: {
2144
+ residentKey: "preferred",
2145
+ userVerification: "required"
2146
+ },
2147
+ extensions: {
2148
+ prf: { eval: { first: toBufferSource(prfSalt) } }
2149
+ }
2150
+ }
2151
+ });
2152
+ if (!credential || !(credential.response instanceof AuthenticatorAttestationResponse)) {
2153
+ throw new EmbeddedWalletVaultError("Passkey registration returned no credential");
2154
+ }
2155
+ const prfOutput = readPrfFromExtensions(credential.getClientExtensionResults());
2156
+ return {
2157
+ credentialId: new Uint8Array(credential.rawId),
2158
+ authenticatorData: new Uint8Array(credential.response.getAuthenticatorData()),
2159
+ clientDataJSON: new Uint8Array(credential.response.clientDataJSON),
2160
+ prfOutput,
2161
+ prfSalt
2162
+ };
2163
+ } catch (err) {
2164
+ throw mapWebAuthnError(err);
2165
+ }
2166
+ }
2167
+ async function assertPasskey(options) {
2168
+ if (typeof navigator === "undefined" || !navigator.credentials?.get) {
2169
+ throw new WebAuthnUnavailableError();
2170
+ }
2171
+ const rpId = resolveRpId(options.rpId);
2172
+ const extensions = {};
2173
+ if (options.prfSalt) {
2174
+ extensions.prf = { eval: { first: toBufferSource(options.prfSalt) } };
2175
+ }
2176
+ try {
2177
+ const assertion = await navigator.credentials.get({
2178
+ publicKey: {
2179
+ challenge: randomChallenge(),
2180
+ rpId,
2181
+ allowCredentials: [{ id: toBufferSource(options.credentialId), type: "public-key" }],
2182
+ userVerification: "required",
2183
+ extensions
2184
+ }
2185
+ });
2186
+ if (!assertion || !(assertion.response instanceof AuthenticatorAssertionResponse)) {
2187
+ throw new EmbeddedWalletVaultError("Passkey assertion returned no credential");
2188
+ }
2189
+ return {
2190
+ credentialId: new Uint8Array(assertion.rawId),
2191
+ authenticatorData: new Uint8Array(assertion.response.authenticatorData),
2192
+ clientDataJSON: new Uint8Array(assertion.response.clientDataJSON),
2193
+ prfOutput: readPrfFromExtensions(assertion.getClientExtensionResults())
2194
+ };
2195
+ } catch (err) {
2196
+ throw mapWebAuthnError(err);
2197
+ }
2198
+ }
2199
+ function readPrfFromExtensions(results) {
2200
+ const prf = results?.prf;
2201
+ const first = prf?.results?.first;
2202
+ if (!first || first.byteLength < 32) {
2203
+ return null;
2204
+ }
2205
+ return new Uint8Array(first).slice(0, 32);
2206
+ }
2207
+ function encodeCredentialId(id) {
2208
+ return toBase64(id);
2209
+ }
2210
+ function decodeCredentialId(encoded) {
2211
+ return fromBase64(encoded);
2212
+ }
2213
+ var DEFAULT_HD_PATH = "m/44'/118'/0'/0/0";
2214
+ async function embeddedPlaintextToSigner(plaintext, prefix = BECH32_PREFIX) {
2215
+ if (plaintext.privateKey.length !== 32) {
2216
+ throw new EmbeddedWalletVaultError(
2217
+ `Invalid secp256k1 private key length: ${plaintext.privateKey.length}`
2218
+ );
2219
+ }
2220
+ return DirectSecp256k1Wallet.fromKey(plaintext.privateKey, prefix);
2221
+ }
2222
+ function defaultEmbeddedHdPath() {
2223
+ return DEFAULT_HD_PATH;
2224
+ }
2225
+
2226
+ // src/embeddedWallet/storage.ts
2227
+ var DB_NAME = "kingdom-os-wallet";
2228
+ var DB_VERSION = 1;
2229
+ var STORE_NAME = "vault";
2230
+ var PRIMARY_KEY = "primary";
2231
+ function openDb() {
2232
+ return new Promise((resolve, reject) => {
2233
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
2234
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB open failed"));
2235
+ request.onupgradeneeded = () => {
2236
+ const db = request.result;
2237
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
2238
+ db.createObjectStore(STORE_NAME);
2239
+ }
2240
+ };
2241
+ request.onsuccess = () => resolve(request.result);
2242
+ });
2243
+ }
2244
+ async function saveVaultToIndexedDb(vault) {
2245
+ const db = await openDb();
2246
+ await new Promise((resolve, reject) => {
2247
+ const tx = db.transaction(STORE_NAME, "readwrite");
2248
+ tx.objectStore(STORE_NAME).put(vault, PRIMARY_KEY);
2249
+ tx.oncomplete = () => resolve();
2250
+ tx.onerror = () => reject(tx.error ?? new Error("IndexedDB write failed"));
2251
+ });
2252
+ db.close();
2253
+ }
2254
+ async function loadVaultFromIndexedDb() {
2255
+ const db = await openDb();
2256
+ const vault = await new Promise((resolve, reject) => {
2257
+ const tx = db.transaction(STORE_NAME, "readonly");
2258
+ const request = tx.objectStore(STORE_NAME).get(PRIMARY_KEY);
2259
+ request.onsuccess = () => resolve(request.result ?? null);
2260
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB read failed"));
2261
+ });
2262
+ db.close();
2263
+ return vault;
2264
+ }
2265
+ async function clearVaultFromIndexedDb() {
2266
+ const db = await openDb();
2267
+ await new Promise((resolve, reject) => {
2268
+ const tx = db.transaction(STORE_NAME, "readwrite");
2269
+ tx.objectStore(STORE_NAME).delete(PRIMARY_KEY);
2270
+ tx.oncomplete = () => resolve();
2271
+ tx.onerror = () => reject(tx.error ?? new Error("IndexedDB delete failed"));
2272
+ });
2273
+ db.close();
2274
+ }
2275
+ var activeSession = null;
2276
+ async function deriveKekFromPasskeyMaterial(vault, prfOutput, authenticatorData, clientDataJSON) {
2277
+ const caps = detectWebAuthnPrfCapabilities();
2278
+ const kdf = selectEmbeddedWalletKdf(caps, prfOutput);
2279
+ if (kdf === EMBEDDED_WALLET_KDF_PRF && prfOutput) {
2280
+ return deriveKekFromPrf(prfOutput);
2281
+ }
2282
+ if (vault.kdf !== EMBEDDED_WALLET_KDF_PBKDF2 || !vault.kdfSalt) {
2283
+ throw new EmbeddedWalletVaultError("Vault expects PBKDF2 but passkey material unavailable");
2284
+ }
2285
+ const salt = decodeCredentialId(vault.kdfSalt);
2286
+ const material = createPasskeyMaterialFromAssertion(authenticatorData, clientDataJSON);
2287
+ return deriveKekFromPbkdf2(material, salt, vault.kdfIterations || void 0);
2288
+ }
2289
+ async function unlockVaultToSession(vault) {
2290
+ const credentialId = decodeCredentialId(vault.credentialId);
2291
+ const prfSalt = vault.prfSalt ? decodeCredentialId(vault.prfSalt) : void 0;
2292
+ const assertion = await assertPasskey({ credentialId, rpId: void 0, prfSalt });
2293
+ const kek = await deriveKekFromPasskeyMaterial(
2294
+ vault,
2295
+ assertion.prfOutput,
2296
+ assertion.authenticatorData,
2297
+ assertion.clientDataJSON
2298
+ );
2299
+ const plaintext = await decryptWalletPayload(vault, kek);
2300
+ const signer = await embeddedPlaintextToSigner(plaintext);
2301
+ const [account] = await signer.getAccounts();
2302
+ const session = { address: account.address, vault, signer };
2303
+ activeSession = session;
2304
+ return session;
2305
+ }
2306
+ async function hasEmbeddedWallet() {
2307
+ const vault = await loadVaultFromIndexedDb();
2308
+ return vault != null;
2309
+ }
2310
+ async function createEmbeddedWallet(options) {
2311
+ const registration = await registerPasskey({
2312
+ userId: options.userId,
2313
+ userName: options.userName,
2314
+ displayName: options.displayName,
2315
+ rpId: options.rpId
2316
+ });
2317
+ const privateKey = Random.getBytes(32);
2318
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2319
+ const plaintext = {
2320
+ privateKey,
2321
+ hdPath: defaultEmbeddedHdPath(),
2322
+ createdAt
2323
+ };
2324
+ const previewSigner = await embeddedPlaintextToSigner(plaintext);
2325
+ const [account] = await previewSigner.getAccounts();
2326
+ const caps = detectWebAuthnPrfCapabilities();
2327
+ const kdf = selectEmbeddedWalletKdf(caps, registration.prfOutput);
2328
+ let kek;
2329
+ let kdfSalt;
2330
+ let prfSalt;
2331
+ if (kdf === EMBEDDED_WALLET_KDF_PRF && registration.prfOutput) {
2332
+ kek = await deriveKekFromPrf(registration.prfOutput);
2333
+ prfSalt = registration.prfSalt;
2334
+ } else {
2335
+ kdfSalt = createPbkdf2Salt();
2336
+ const material = createPasskeyMaterialFromAssertion(
2337
+ registration.authenticatorData,
2338
+ registration.clientDataJSON
2339
+ );
2340
+ kek = await deriveKekFromPbkdf2(material, kdfSalt);
2341
+ }
2342
+ const vault = await encryptWalletPayload(plaintext, {
2343
+ kdf,
2344
+ credentialId: registration.credentialId,
2345
+ publicAddress: account.address,
2346
+ kek,
2347
+ kdfSalt,
2348
+ prfSalt,
2349
+ backupAcknowledged: false
2350
+ });
2351
+ await saveVaultToIndexedDb(vault);
2352
+ lockEmbeddedWallet();
2353
+ return { address: account.address, vault };
2354
+ }
2355
+ async function unlockEmbeddedWallet() {
2356
+ const vault = await loadVaultFromIndexedDb();
2357
+ if (!vault) {
2358
+ throw new EmbeddedWalletVaultError("No embedded wallet found on this device");
2359
+ }
2360
+ return unlockVaultToSession(vault);
2361
+ }
2362
+ function lockEmbeddedWallet() {
2363
+ activeSession = null;
2364
+ }
2365
+ function getEmbeddedWalletSession() {
2366
+ return activeSession;
2367
+ }
2368
+ function getEmbeddedOfflineSigner() {
2369
+ if (!activeSession) {
2370
+ throw new EmbeddedWalletVaultError("Embedded wallet is locked");
2371
+ }
2372
+ assertVaultPermitsTransaction(activeSession.vault);
2373
+ const inner = activeSession.signer;
2374
+ return {
2375
+ getAccounts: () => inner.getAccounts(),
2376
+ signDirect: (signerAddress, signDoc) => inner.signDirect(signerAddress, signDoc)
2377
+ };
2378
+ }
2379
+ async function acknowledgeEmbeddedBackup() {
2380
+ const vault = await loadVaultFromIndexedDb();
2381
+ if (!vault) {
2382
+ throw new EmbeddedWalletVaultError("No embedded wallet found on this device");
2383
+ }
2384
+ const updated = markVaultBackupAcknowledged(vault);
2385
+ await saveVaultToIndexedDb(updated);
2386
+ if (activeSession) {
2387
+ activeSession = { ...activeSession, vault: updated };
2388
+ }
2389
+ return exportEncryptedBackup(updated);
2390
+ }
2391
+ function downloadEmbeddedBackupFile(backup, filename) {
2392
+ const blob = new Blob([serializeBackupFile(backup)], { type: "application/json" });
2393
+ const url = URL.createObjectURL(blob);
2394
+ const anchor = document.createElement("a");
2395
+ anchor.href = url;
2396
+ anchor.download = filename ?? `kingdom-os-wallet-backup-${backup.vault.publicAddress}.json`;
2397
+ anchor.click();
2398
+ URL.revokeObjectURL(url);
2399
+ }
2400
+ async function importEmbeddedBackup(json) {
2401
+ const backup = parseEncryptedBackup(json);
2402
+ await saveVaultToIndexedDb(backup.vault);
2403
+ lockEmbeddedWallet();
2404
+ return backup.vault;
2405
+ }
2406
+ async function clearEmbeddedWallet() {
2407
+ lockEmbeddedWallet();
2408
+ await clearVaultFromIndexedDb();
2409
+ }
1680
2410
 
1681
2411
  // src/submitApplication.ts
1682
2412
  function buildSubmitApplicationMsg(applicantWallet, applicationId, attestationInput, signature) {
@@ -1718,6 +2448,6 @@ async function signCitizenshipAttestation(params) {
1718
2448
  return { signature, input };
1719
2449
  }
1720
2450
 
1721
- export { ATTESTATION_TYPE_CITIZENSHIP_APPLICATION, ATTESTATION_TYPE_RECOVERY, ApplicationStatus, BECH32_PREFIX, CHAIN_COIN_TYPE, DEFAULT_CHAIN_ID, DEFAULT_REST_URL, DEFAULT_RPC_URL, InvalidChainConfigError, MsgSubmitApplication, MsgSubmitRecoveryAttestation, RecoveryAttestationStatus, RpcConnectionError, UKDR_DECIMALS, UKDR_DISPLAY_DENOM, UKDR_MINIMAL_DENOM, WalletAccountError, WalletConnectionError, WalletConnectionRejectedError, WalletNotInstalledError, attestationSignPayload, buildKingdomChainInfo, buildSubmitApplicationMsg, connectKeplr, connectLeap, createBrowserSigningClient, createKingdomChainRegistry, isKeplrAvailable, isLeapAvailable, providerSeedFromHex, publicKeyFromPrivateSeed, signAttestationPayload, signCitizenshipAttestation, suggestAndConnect, validateChainConfig };
2451
+ export { ATTESTATION_TYPE_CITIZENSHIP_APPLICATION, ATTESTATION_TYPE_RECOVERY, ApplicationStatus, BECH32_PREFIX, CHAIN_COIN_TYPE, DEFAULT_CHAIN_ID, DEFAULT_REST_URL, DEFAULT_RPC_URL, EMBEDDED_WALLET_KDF_PBKDF2, EMBEDDED_WALLET_KDF_PRF, EMBEDDED_WALLET_VAULT_VERSION, EmbeddedWalletBackupRequiredError, EmbeddedWalletVaultError, InvalidChainConfigError, MsgSubmitApplication, MsgSubmitRecoveryAttestation, RecoveryAttestationStatus, RpcConnectionError, UKDR_DECIMALS, UKDR_DISPLAY_DENOM, UKDR_MINIMAL_DENOM, WALLETCONNECT_COSMOS_METHODS, WALLETCONNECT_COSMOS_NAMESPACE, WEBAUTHN_RP_NAME, WalletAccountError, WalletConnectNotInitializedError, WalletConnectSessionError, WalletConnectSigningError, WalletConnectionError, WalletConnectionRejectedError, WalletNotInstalledError, WebAuthnUnavailableError, WebAuthnUserCancelledError, acknowledgeEmbeddedBackup, assertPasskey, assertVaultPermitsTransaction, attestationSignPayload, buildKingdomChainInfo, buildRequiredCosmosNamespaces, buildSubmitApplicationMsg, clearEmbeddedWallet, clearVaultFromIndexedDb, connectCosmostation, connectKeplr, connectLeap, connectWalletConnect, createBrowserSigningClient, createEmbeddedWallet, createKingdomChainRegistry, createPasskeyMaterialFromAssertion, createPbkdf2Salt, createWalletConnectOfflineSigner, decodeCredentialId, decryptWalletPayload, defaultEmbeddedHdPath, deriveKekFromPbkdf2, deriveKekFromPrf, detectWebAuthnPrfCapabilities, disconnectWalletConnect, downloadEmbeddedBackupFile, embeddedPlaintextToSigner, encodeCredentialId, encryptWalletPayload, exportEncryptedBackup, getCosmostationKeplrApi, getEmbeddedOfflineSigner, getEmbeddedWalletSession, hasEmbeddedWallet, importEmbeddedBackup, initWalletConnectClient, isCosmostationAvailable, isKeplrAvailable, isLeapAvailable, loadVaultFromIndexedDb, lockEmbeddedWallet, markVaultBackupAcknowledged, parseEncryptedBackup, parseWalletConnectAccount, providerSeedFromHex, publicKeyFromPrivateSeed, readPrfOutput, registerPasskey, restoreWalletConnectSession, saveVaultToIndexedDb, selectEmbeddedWalletKdf, serializeBackupFile, signAttestationPayload, signCitizenshipAttestation, suggestAndConnect, toWalletConnectCosmosChainId, unlockEmbeddedWallet, validateChainConfig };
1722
2452
  //# sourceMappingURL=index.js.map
1723
2453
  //# sourceMappingURL=index.js.map