Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f41ae5626 | ||
|
|
657bc17eff | ||
|
|
fc82acb4d5 | ||
|
|
490f4ec8a4 | ||
|
|
770f96603b | ||
|
|
cc47ba4636 | ||
|
|
eabdf8ee6f | ||
|
|
438db6d5c6 | ||
|
|
49d454dcf7 | ||
|
|
b6d05b650c | ||
|
|
48565921d8 |
@@ -1,5 +1,3 @@
|
|||||||
REACT_APP_WALLET_CONNECT_PROJECT_ID=
|
|
||||||
|
|
||||||
REACT_APP_DEFAULT_GAS_PRICE=0.025
|
REACT_APP_DEFAULT_GAS_PRICE=0.025
|
||||||
# Reference: https://github.com/cosmos/cosmos-sdk/issues/16020
|
# Reference: https://github.com/cosmos/cosmos-sdk/issues/16020
|
||||||
REACT_APP_GAS_ADJUSTMENT=2
|
REACT_APP_GAS_ADJUSTMENT=2
|
||||||
|
|||||||
@@ -2,18 +2,21 @@ name: Lint and Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main, milestone-3 ]
|
branches: [ main ]
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint-and-build:
|
lint-and-build:
|
||||||
name: Run lint and build checks
|
name: Run lint and build checks
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
@cerc-io:registry=https://git.vdb.to/api/packages/cerc-io/npm/
|
@cerc-io:registry=https://git.vdb.to/api/packages/cerc-io/npm/
|
||||||
|
@laconic-network:registry=https://git.vdb.to/api/packages/LaconicNetwork/npm/
|
||||||
|
|||||||
Executable
+65
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Build and deploy zenith-wallet-web as an Urbit glob.
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - yarn installed
|
||||||
|
# - An Urbit ship running with a pier at $PIER_PATH
|
||||||
|
# - The desk 'zenith-wallet' already created on the ship
|
||||||
|
# - IPFS node running (for glob upload)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# PIER_PATH=/path/to/pier ./build-glob.sh
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DESK_NAME="zenith-wallet"
|
||||||
|
|
||||||
|
if [ -z "${PIER_PATH:-}" ]; then
|
||||||
|
echo "Error: PIER_PATH must be set to your Urbit pier directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DESK_PATH="${PIER_PATH}/${DESK_NAME}"
|
||||||
|
|
||||||
|
# 1. Build the React app
|
||||||
|
echo "==> Building React app..."
|
||||||
|
yarn && yarn build
|
||||||
|
|
||||||
|
# 2. Lowercase all filenames in build/ (Urbit requires lowercase)
|
||||||
|
echo "==> Lowercasing filenames in build/..."
|
||||||
|
find build -depth -name '*[A-Z]*' | while read -r f; do
|
||||||
|
dir="$(dirname "$f")"
|
||||||
|
base="$(basename "$f")"
|
||||||
|
lower="$(echo "$base" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
if [ "$base" != "$lower" ]; then
|
||||||
|
mv "$f" "${dir}/${lower}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3. Copy build output to the ship's desk
|
||||||
|
echo "==> Copying build/ to ${DESK_PATH}..."
|
||||||
|
mkdir -p "${DESK_PATH}"
|
||||||
|
cp -r build/* "${DESK_PATH}/"
|
||||||
|
|
||||||
|
# 4. Copy mark files for non-standard extensions
|
||||||
|
echo "==> Copying mark files..."
|
||||||
|
mkdir -p "${DESK_PATH}/mar"
|
||||||
|
cp urbit-files/mar/*.hoon "${DESK_PATH}/mar/"
|
||||||
|
|
||||||
|
# 5. Copy desk.docket-0
|
||||||
|
echo "==> Copying desk.docket-0..."
|
||||||
|
cp urbit-files/desk.docket-0 "${DESK_PATH}/desk.docket-0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Done! Next steps:"
|
||||||
|
echo " 1. In your ship's dojo, run:"
|
||||||
|
echo " |commit %${DESK_NAME}"
|
||||||
|
echo " 2. Create the glob:"
|
||||||
|
echo " -garden!make-glob %${DESK_NAME} /build"
|
||||||
|
echo " 3. Upload the glob file from .urb/put/ to IPFS"
|
||||||
|
echo " 4. Update desk.docket-0 with the IPFS URL and hash"
|
||||||
|
echo " 5. Recommit:"
|
||||||
|
echo " |commit %${DESK_NAME}"
|
||||||
|
echo " 6. Install the desk:"
|
||||||
|
echo " |install our %${DESK_NAME}"
|
||||||
@@ -54,5 +54,20 @@ module.exports = function override(config, env) {
|
|||||||
|
|
||||||
config.resolve.alias['react-native$'] = require.resolve('react-native-web');
|
config.resolve.alias['react-native$'] = require.resolve('react-native-web');
|
||||||
|
|
||||||
|
// Ignore source map warnings from third-party packages. Ref: https://github.com/facebook/create-react-app/discussions/11767#discussioncomment-3416044
|
||||||
|
const ignoreSourceMapPackages = [
|
||||||
|
'@cosmjs',
|
||||||
|
'@confio/ics23',
|
||||||
|
'@json-rpc-tools',
|
||||||
|
'@pedrouid/environment',
|
||||||
|
'@walletconnect',
|
||||||
|
'cosmjs-types',
|
||||||
|
];
|
||||||
|
|
||||||
|
config.ignoreWarnings = ignoreSourceMapPackages.map(pkg => ({
|
||||||
|
module: new RegExp(`node_modules/${pkg.replace('/', '\\/')}`),
|
||||||
|
message: /Failed to parse source map/,
|
||||||
|
}));
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-5
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "web-wallet",
|
"name": "web-wallet",
|
||||||
"version": "0.1.7-zenith-0.1.1",
|
"version": "0.1.7-zenith-0.2.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@laconic-network/cosmjs-util": "^0.1.0",
|
||||||
"@cerc-io/registry-sdk": "^0.2.5",
|
"@cerc-io/registry-sdk": "^0.2.5",
|
||||||
"@cosmjs/amino": "^0.32.3",
|
"@cosmjs/amino": "^0.33.1",
|
||||||
"@cosmjs/crypto": "^0.32.3",
|
"@cosmjs/crypto": "^0.33.1",
|
||||||
"@cosmjs/encoding": "^0.33.1",
|
"@cosmjs/encoding": "^0.33.1",
|
||||||
"@cosmjs/proto-signing": "^0.32.3",
|
"@cosmjs/proto-signing": "^0.33.1",
|
||||||
"@cosmjs/stargate": "^0.32.3",
|
"@cosmjs/stargate": "^0.33.1",
|
||||||
"@emotion/react": "^11.13.0",
|
"@emotion/react": "^11.13.0",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@ethersproject/shims": "^5.7.0",
|
"@ethersproject/shims": "^5.7.0",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { useNetworks } from "../context/NetworksContext";
|
|||||||
import ConfirmDialog from "./ConfirmDialog";
|
import ConfirmDialog from "./ConfirmDialog";
|
||||||
import { getNamespaces } from "../utils/wallet-connect/helpers";
|
import { getNamespaces } from "../utils/wallet-connect/helpers";
|
||||||
import ShowPKDialog from "./ShowPKDialog";
|
import ShowPKDialog from "./ShowPKDialog";
|
||||||
import { setInternetCredentials } from "../utils/key-store";
|
import { updateNetworks } from "../utils/eyre-client";
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
@@ -110,11 +110,7 @@ const Accounts = () => {
|
|||||||
(networkData) => selectedNetwork!.networkId !== networkData.networkId,
|
(networkData) => selectedNetwork!.networkId !== networkData.networkId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await setInternetCredentials(
|
await updateNetworks(updatedNetworks);
|
||||||
"networks",
|
|
||||||
"_",
|
|
||||||
JSON.stringify(updatedNetworks),
|
|
||||||
);
|
|
||||||
|
|
||||||
setSelectedNetwork(updatedNetworks[0]);
|
setSelectedNetwork(updatedNetworks[0]);
|
||||||
setCurrentIndex(0);
|
setCurrentIndex(0);
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { NetworksDataState } from '../types';
|
import { NetworksDataState } from '../types';
|
||||||
import { retrieveNetworksData } from '../utils/accounts';
|
import { getNetworks, updateNetworks } from '../utils/eyre-client';
|
||||||
import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants';
|
import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants';
|
||||||
import { setInternetCredentials } from '../utils/key-store';
|
|
||||||
|
|
||||||
const NetworksContext = createContext<{
|
const NetworksContext = createContext<{
|
||||||
networksData: NetworksDataState[];
|
networksData: NetworksDataState[];
|
||||||
@@ -42,15 +41,10 @@ const NetworksProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
let retrievedNetworks = await retrieveNetworksData();
|
let retrievedNetworks = await getNetworks();
|
||||||
|
|
||||||
if (retrievedNetworks.length === 0) {
|
if (retrievedNetworks.length === 0) {
|
||||||
setInternetCredentials(
|
await updateNetworks(DEFAULT_NETWORKS_DATA);
|
||||||
'networks',
|
|
||||||
'_',
|
|
||||||
JSON.stringify(DEFAULT_NETWORKS_DATA),
|
|
||||||
);
|
|
||||||
|
|
||||||
retrievedNetworks = DEFAULT_NETWORKS_DATA;
|
retrievedNetworks = DEFAULT_NETWORKS_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export default function useInitialization(
|
|||||||
}, [setWeb3wallet]);
|
}, [setWeb3wallet]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!import.meta.env.REACT_APP_WALLET_CONNECT_PROJECT_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
onInitialize();
|
onInitialize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import {
|
|||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { DirectSecp256k1Wallet, EncodeObject } from '@cosmjs/proto-signing';
|
import { DirectSecp256k1Wallet, EncodeObject } from '@cosmjs/proto-signing';
|
||||||
import { LaconicClient } from '@cerc-io/registry-sdk';
|
import { LaconicClient } from '@cerc-io/registry-sdk';
|
||||||
import { GasPrice, calculateFee } from '@cosmjs/stargate';
|
import { calculateFee } from '@cosmjs/stargate';
|
||||||
import { formatJsonRpcError } from '@json-rpc-tools/utils';
|
import { formatJsonRpcError } from '@json-rpc-tools/utils';
|
||||||
|
|
||||||
|
import { parseCustomGasPrice } from '@laconic-network/cosmjs-util';
|
||||||
|
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
import { Account, StackParamsList } from '../types';
|
import { Account, StackParamsList } from '../types';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
@@ -177,7 +179,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
}, [cosmosStargateClient, transactionMessage, requestEventId, topic, web3wallet, signer]);
|
}, [cosmosStargateClient, transactionMessage, requestEventId, topic, web3wallet, signer]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const gasPrice = GasPrice.fromString(
|
const gasPrice = parseCustomGasPrice(
|
||||||
requestedNetwork?.gasPrice! + requestedNetwork?.nativeDenom,
|
requestedNetwork?.gasPrice! + requestedNetwork?.nativeDenom,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ import { getHeaderTitle } from '@react-navigation/elements';
|
|||||||
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
|
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
|
||||||
import {
|
import {
|
||||||
calculateFee,
|
calculateFee,
|
||||||
GasPrice,
|
|
||||||
MsgSendEncodeObject,
|
MsgSendEncodeObject,
|
||||||
SigningStargateClient,
|
SigningStargateClient,
|
||||||
} from '@cosmjs/stargate';
|
} from '@cosmjs/stargate';
|
||||||
|
|
||||||
|
import { parseCustomGasPrice } from '@laconic-network/cosmjs-util';
|
||||||
|
|
||||||
import { Account, StackParamsList } from '../types';
|
import { Account, StackParamsList } from '../types';
|
||||||
import AccountDetails from '../components/AccountDetails';
|
import AccountDetails from '../components/AccountDetails';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
@@ -225,7 +226,7 @@ const ApproveTransfer = ({ route }: SignRequestProps) => {
|
|||||||
.toString();
|
.toString();
|
||||||
setFees(ethFees);
|
setFees(ethFees);
|
||||||
} else {
|
} else {
|
||||||
const gasPrice = GasPrice.fromString(
|
const gasPrice = parseCustomGasPrice(
|
||||||
requestedNetwork?.gasPrice! + requestedNetwork?.nativeDenom,
|
requestedNetwork?.gasPrice! + requestedNetwork?.nativeDenom,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ import {
|
|||||||
} from "@react-navigation/native-stack";
|
} from "@react-navigation/native-stack";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useNavigation } from "@react-navigation/native";
|
||||||
|
|
||||||
import { setInternetCredentials } from "../utils/key-store";
|
|
||||||
import { StackParamsList } from "../types";
|
import { StackParamsList } from "../types";
|
||||||
import { retrieveNetworksData } from "../utils/accounts";
|
import { getNetworks, updateNetworks } from "../utils/eyre-client";
|
||||||
import { useNetworks } from "../context/NetworksContext";
|
import { useNetworks } from "../context/NetworksContext";
|
||||||
import {
|
import {
|
||||||
COSMOS,
|
COSMOS,
|
||||||
@@ -74,7 +73,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
|
|||||||
|
|
||||||
const submit = useCallback(
|
const submit = useCallback(
|
||||||
async (data: z.infer<typeof networksFormDataSchema>) => {
|
async (data: z.infer<typeof networksFormDataSchema>) => {
|
||||||
const retrievedNetworksData = await retrieveNetworksData();
|
const retrievedNetworksData = await getNetworks();
|
||||||
const { type, ...dataWithoutType } = data;
|
const { type, ...dataWithoutType } = data;
|
||||||
const newNetworkData = { ...networkData, ...dataWithoutType };
|
const newNetworkData = { ...networkData, ...dataWithoutType };
|
||||||
const index = retrievedNetworksData.findIndex(
|
const index = retrievedNetworksData.findIndex(
|
||||||
@@ -83,11 +82,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
|
|||||||
|
|
||||||
retrievedNetworksData.splice(index, 1, newNetworkData);
|
retrievedNetworksData.splice(index, 1, newNetworkData);
|
||||||
|
|
||||||
await setInternetCredentials(
|
await updateNetworks(retrievedNetworksData);
|
||||||
"networks",
|
|
||||||
"_",
|
|
||||||
JSON.stringify(retrievedNetworksData),
|
|
||||||
);
|
|
||||||
|
|
||||||
setNetworksData(retrievedNetworksData);
|
setNetworksData(retrievedNetworksData);
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import { BigNumber } from 'ethers';
|
|||||||
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
|
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
|
||||||
import {
|
import {
|
||||||
calculateFee,
|
calculateFee,
|
||||||
GasPrice,
|
|
||||||
SigningStargateClient,
|
SigningStargateClient,
|
||||||
} from '@cosmjs/stargate';
|
} from '@cosmjs/stargate';
|
||||||
|
|
||||||
|
import { parseCustomGasPrice } from '@laconic-network/cosmjs-util';
|
||||||
|
|
||||||
import { retrieveSingleAccount } from '../utils/accounts';
|
import { retrieveSingleAccount } from '../utils/accounts';
|
||||||
import AccountDetails from '../components/AccountDetails';
|
import AccountDetails from '../components/AccountDetails';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
@@ -149,7 +150,7 @@ export const WalletEmbed = () => {
|
|||||||
);
|
);
|
||||||
setGasLimit(gasLimit);
|
setGasLimit(gasLimit);
|
||||||
|
|
||||||
const gasPrice = GasPrice.fromString(`${network.gasPrice}${network.nativeDenom}`);
|
const gasPrice = parseCustomGasPrice(`${network.gasPrice}${network.nativeDenom}`);
|
||||||
const cosmosFees = calculateFee(Number(gasLimit), gasPrice);
|
const cosmosFees = calculateFee(Number(gasLimit), gasPrice);
|
||||||
setFees(cosmosFees.amount[0].amount);
|
setFees(cosmosFees.amount[0].amount);
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ export const WalletEmbed = () => {
|
|||||||
|
|
||||||
const fee = calculateFee(
|
const fee = calculateFee(
|
||||||
Number(gasLimit),
|
Number(gasLimit),
|
||||||
GasPrice.fromString(`${transactionDetails.requestedNetwork.gasPrice}${transactionDetails.requestedNetwork.nativeDenom}`)
|
parseCustomGasPrice(`${transactionDetails.requestedNetwork.gasPrice}${transactionDetails.requestedNetwork.nativeDenom}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
const txResult = await client.sendTokens(
|
const txResult = await client.sendTokens(
|
||||||
|
|||||||
@@ -0,0 +1,457 @@
|
|||||||
|
/**
|
||||||
|
* Tests for eyre-client.ts — the Eyre HTTP client for %zenith agent.
|
||||||
|
*
|
||||||
|
* Mocks: global fetch, EventSource
|
||||||
|
* Strategy: jest.resetModules() + dynamic import per test group for clean
|
||||||
|
* module-level state (channelUid, cachedShipName, messageId, pendingPokes).
|
||||||
|
*/
|
||||||
|
|
||||||
|
type EyreClient = typeof import('../eyre-client');
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Mock EventSource
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ESListener = (ev: MessageEvent) => void;
|
||||||
|
type ESErrorListener = (ev: Event) => void;
|
||||||
|
|
||||||
|
class MockEventSource {
|
||||||
|
static instance: MockEventSource | null = null;
|
||||||
|
|
||||||
|
url: string;
|
||||||
|
withCredentials: boolean;
|
||||||
|
private listeners: Record<string, Array<ESListener | ESErrorListener>> = {};
|
||||||
|
close = jest.fn();
|
||||||
|
|
||||||
|
constructor(url: string, opts?: { withCredentials?: boolean }) {
|
||||||
|
this.url = url;
|
||||||
|
this.withCredentials = opts?.withCredentials ?? false;
|
||||||
|
MockEventSource.instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener(type: string, cb: ESListener | ESErrorListener) {
|
||||||
|
(this.listeners[type] ??= []).push(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Simulate an SSE message event */
|
||||||
|
emit(type: string, data: unknown, lastEventId = '1') {
|
||||||
|
for (const cb of this.listeners[type] ?? []) {
|
||||||
|
(cb as ESListener)({
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
lastEventId,
|
||||||
|
} as MessageEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitError() {
|
||||||
|
for (const cb of this.listeners['error'] ?? []) {
|
||||||
|
(cb as ESErrorListener)(new Event('error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install MockEventSource globally before any module loads
|
||||||
|
(global as any).EventSource = MockEventSource;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function mockFetchResponses(
|
||||||
|
...responses: Array<{ status: number; body?: unknown; text?: string }>
|
||||||
|
) {
|
||||||
|
const impl = jest.fn();
|
||||||
|
for (const r of responses) {
|
||||||
|
impl.mockResolvedValueOnce({
|
||||||
|
ok: r.status >= 200 && r.status < 300,
|
||||||
|
status: r.status,
|
||||||
|
statusText: r.status === 404 ? 'Not Found' : 'OK',
|
||||||
|
json: () => Promise.resolve(r.body),
|
||||||
|
text: () => Promise.resolve(r.text ?? ''),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
global.fetch = impl;
|
||||||
|
return impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadModule(): Promise<EyreClient> {
|
||||||
|
jest.resetModules();
|
||||||
|
MockEventSource.instance = null;
|
||||||
|
return import('../eyre-client');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Tests
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('eyre-client', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// scryGet
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
describe('scryGet', () => {
|
||||||
|
it('returns parsed JSON on 200', async () => {
|
||||||
|
const { scryGet } = await loadModule();
|
||||||
|
mockFetchResponses({ status: 200, body: { mnemonic: 'foo bar' } });
|
||||||
|
|
||||||
|
const result = await scryGet('/wallet/mnemonic');
|
||||||
|
|
||||||
|
expect(result).toEqual({ mnemonic: 'foo bar' });
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
'/~/scry/zenith/wallet/mnemonic.json',
|
||||||
|
{ credentials: 'include' },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null on 404', async () => {
|
||||||
|
const { scryGet } = await loadModule();
|
||||||
|
mockFetchResponses({ status: 404 });
|
||||||
|
|
||||||
|
const result = await scryGet('/wallet/nonexistent');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on server error', async () => {
|
||||||
|
const { scryGet } = await loadModule();
|
||||||
|
mockFetchResponses({ status: 500 });
|
||||||
|
|
||||||
|
await expect(scryGet('/wallet/broken')).rejects.toThrow(
|
||||||
|
'Scry /wallet/broken failed: 500',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// pokeAction
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
describe('pokeAction', () => {
|
||||||
|
it('sends correct channel PUT body and resolves on ack', async () => {
|
||||||
|
const { pokeAction } = await loadModule();
|
||||||
|
|
||||||
|
// First fetch: getShipName() → /~/host
|
||||||
|
// Second fetch: channel PUT
|
||||||
|
// Third fetch: ack PUT (from EventSource handler)
|
||||||
|
const fetchMock = mockFetchResponses(
|
||||||
|
{ status: 200, text: '~sampel-palnet' },
|
||||||
|
{ status: 200 },
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
|
||||||
|
const pokePromise = pokeAction('json', { action: 'test' });
|
||||||
|
|
||||||
|
// Wait for fetch calls to settle
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
|
// Verify channel PUT body
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||||
|
const putCall = fetchMock.mock.calls[1];
|
||||||
|
expect(putCall[0]).toMatch(/^\/~\/channel\/wallet-\d+$/);
|
||||||
|
expect(putCall[1].method).toBe('PUT');
|
||||||
|
|
||||||
|
const putBody = JSON.parse(putCall[1].body);
|
||||||
|
expect(putBody).toHaveLength(1);
|
||||||
|
expect(putBody[0]).toMatchObject({
|
||||||
|
action: 'poke',
|
||||||
|
ship: '~sampel-palnet',
|
||||||
|
app: 'zenith',
|
||||||
|
mark: 'json',
|
||||||
|
json: { action: 'test' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate poke ack from EventSource
|
||||||
|
const es = MockEventSource.instance!;
|
||||||
|
es.emit('message', { response: 'poke', id: putBody[0].id, ok: 'ok' });
|
||||||
|
|
||||||
|
await expect(pokePromise).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects on poke nack', async () => {
|
||||||
|
const { pokeAction } = await loadModule();
|
||||||
|
|
||||||
|
mockFetchResponses(
|
||||||
|
{ status: 200, text: '~zod' },
|
||||||
|
{ status: 200 },
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
|
||||||
|
const pokePromise = pokeAction('json', { action: 'bad' });
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
|
const putBody = JSON.parse(
|
||||||
|
(global.fetch as jest.Mock).mock.calls[1][1].body,
|
||||||
|
);
|
||||||
|
const es = MockEventSource.instance!;
|
||||||
|
es.emit('message', {
|
||||||
|
response: 'poke',
|
||||||
|
id: putBody[0].id,
|
||||||
|
ok: 'err',
|
||||||
|
err: 'Invalid mark',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(pokePromise).rejects.toThrow('Invalid mark');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects when channel PUT fails', async () => {
|
||||||
|
const { pokeAction } = await loadModule();
|
||||||
|
|
||||||
|
mockFetchResponses(
|
||||||
|
{ status: 200, text: '~zod' },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
pokeAction('json', { action: 'fail' }),
|
||||||
|
).rejects.toThrow('Channel PUT failed: 500');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects all pending pokes on EventSource error', async () => {
|
||||||
|
const { pokeAction } = await loadModule();
|
||||||
|
|
||||||
|
mockFetchResponses(
|
||||||
|
{ status: 200, text: '~zod' },
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
|
||||||
|
const pokePromise = pokeAction('json', { action: 'x' });
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
|
MockEventSource.instance!.emitError();
|
||||||
|
|
||||||
|
await expect(pokePromise).rejects.toThrow('Eyre channel disconnected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// getWalletData (compatibility layer)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
describe('getWalletData', () => {
|
||||||
|
it('delegates mnemonicServer to scry /wallet/mnemonic', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
mockFetchResponses({ status: 200, body: { mnemonic: 'test phrase' } });
|
||||||
|
|
||||||
|
const result = await getWalletData('mnemonicServer');
|
||||||
|
|
||||||
|
expect(result).toBe('test phrase');
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
'/~/scry/zenith/wallet/mnemonic.json',
|
||||||
|
{ credentials: 'include' },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when no mnemonic exists', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
mockFetchResponses({ status: 404 });
|
||||||
|
|
||||||
|
expect(await getWalletData('mnemonicServer')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delegates networks to scry /wallet/networks', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
const networks = [{ networkId: 'zenith-1', networkName: 'Zenith' }];
|
||||||
|
mockFetchResponses({ status: 200, body: networks });
|
||||||
|
|
||||||
|
const result = await getWalletData('networks');
|
||||||
|
|
||||||
|
expect(result).toBe(JSON.stringify(networks));
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
'/~/scry/zenith/wallet/networks.json',
|
||||||
|
{ credentials: 'include' },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when no networks exist', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
mockFetchResponses({ status: 200, body: [] });
|
||||||
|
|
||||||
|
expect(await getWalletData('networks')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delegates accounts/eip155:1/0 to scry /wallet/account/eip155:1/0', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
const account = {
|
||||||
|
index: 0,
|
||||||
|
hdPath: "m/44'/60'/0'/0/0",
|
||||||
|
privKey: '0xabc',
|
||||||
|
pubKey: '0xdef',
|
||||||
|
address: '0x123',
|
||||||
|
};
|
||||||
|
mockFetchResponses({ status: 200, body: account });
|
||||||
|
|
||||||
|
const result = await getWalletData('accounts/eip155:1/0');
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
`${account.hdPath},${account.privKey},${account.pubKey},${account.address}`,
|
||||||
|
);
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
'/~/scry/zenith/wallet/account/eip155:1/0.json',
|
||||||
|
{ credentials: 'include' },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null for nonexistent account', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
mockFetchResponses({ status: 404 });
|
||||||
|
|
||||||
|
expect(await getWalletData('accounts/eip155:1/99')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delegates accountIndices to scry /wallet/accounts', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
const accounts = [
|
||||||
|
{ index: 0, pubKey: 'a', address: 'b', hdPath: 'c' },
|
||||||
|
{ index: 1, pubKey: 'd', address: 'e', hdPath: 'f' },
|
||||||
|
];
|
||||||
|
mockFetchResponses({ status: 200, body: accounts });
|
||||||
|
|
||||||
|
const result = await getWalletData('accountIndices/cosmos:zenith-1');
|
||||||
|
|
||||||
|
expect(result).toBe('0,1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delegates addAccountCounter to scry next-account-id', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
mockFetchResponses({ status: 200, body: { id: 3 } });
|
||||||
|
|
||||||
|
const result = await getWalletData('addAccountCounter/cosmos:zenith-1');
|
||||||
|
|
||||||
|
expect(result).toBe('3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null for unknown key', async () => {
|
||||||
|
const { getWalletData } = await loadModule();
|
||||||
|
|
||||||
|
expect(await getWalletData('unknown-key')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// setWalletData (compatibility layer)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
describe('setWalletData', () => {
|
||||||
|
it('delegates networks write to poke with update-networks action', async () => {
|
||||||
|
const { setWalletData } = await loadModule();
|
||||||
|
const networks = [{ networkId: 'zenith-1', networkName: 'Zenith' }];
|
||||||
|
|
||||||
|
// getShipName fetch, channel PUT, ack fetch
|
||||||
|
const fetchMock = mockFetchResponses(
|
||||||
|
{ status: 200, text: '~zod' },
|
||||||
|
{ status: 200 },
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
|
||||||
|
const promise = setWalletData('networks', JSON.stringify(networks));
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
|
// Verify the poke body
|
||||||
|
const putBody = JSON.parse(fetchMock.mock.calls[1][1].body);
|
||||||
|
expect(putBody[0]).toMatchObject({
|
||||||
|
action: 'poke',
|
||||||
|
mark: 'json',
|
||||||
|
json: { action: 'update-networks', networks },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ack to resolve
|
||||||
|
MockEventSource.instance!.emit('message', {
|
||||||
|
response: 'poke',
|
||||||
|
id: putBody[0].id,
|
||||||
|
ok: 'ok',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(promise).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on direct mnemonic write', async () => {
|
||||||
|
const { setWalletData } = await loadModule();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
setWalletData('mnemonicServer', 'some words'),
|
||||||
|
).rejects.toThrow('Cannot set mnemonic directly');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on direct account write', async () => {
|
||||||
|
const { setWalletData } = await loadModule();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
setWalletData('accounts/eip155:1/0', 'data'),
|
||||||
|
).rejects.toThrow('Direct write to');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on unknown key', async () => {
|
||||||
|
const { setWalletData } = await loadModule();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
setWalletData('unknown', 'data'),
|
||||||
|
).rejects.toThrow('Unknown wallet data key');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// deleteWalletData
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
describe('deleteWalletData', () => {
|
||||||
|
it('delegates mnemonicServer delete to resetWallet poke', async () => {
|
||||||
|
const { deleteWalletData } = await loadModule();
|
||||||
|
|
||||||
|
const fetchMock = mockFetchResponses(
|
||||||
|
{ status: 200, text: '~zod' },
|
||||||
|
{ status: 200 },
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
|
||||||
|
const promise = deleteWalletData('mnemonicServer');
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
|
const putBody = JSON.parse(fetchMock.mock.calls[1][1].body);
|
||||||
|
expect(putBody[0].json).toEqual({ action: 'reset-wallet' });
|
||||||
|
|
||||||
|
MockEventSource.instance!.emit('message', {
|
||||||
|
response: 'poke',
|
||||||
|
id: putBody[0].id,
|
||||||
|
ok: 'ok',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(promise).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is a no-op for account keys', async () => {
|
||||||
|
const { deleteWalletData } = await loadModule();
|
||||||
|
|
||||||
|
// Should resolve without making any fetch calls
|
||||||
|
await expect(
|
||||||
|
deleteWalletData('accounts/eip155:1/0'),
|
||||||
|
).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// closeChannel
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
describe('closeChannel', () => {
|
||||||
|
it('closes EventSource and rejects pending pokes', async () => {
|
||||||
|
const { pokeAction, closeChannel } = await loadModule();
|
||||||
|
|
||||||
|
mockFetchResponses(
|
||||||
|
{ status: 200, text: '~zod' },
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
|
||||||
|
const pokePromise = pokeAction('json', { action: 'x' });
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
|
closeChannel();
|
||||||
|
|
||||||
|
await expect(pokePromise).rejects.toThrow('Channel closed');
|
||||||
|
expect(MockEventSource.instance!.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+52
-301
@@ -7,106 +7,54 @@ import '@ethersproject/shims';
|
|||||||
import { utils } from 'ethers';
|
import { utils } from 'ethers';
|
||||||
import { HDNode } from 'ethers/lib/utils';
|
import { HDNode } from 'ethers/lib/utils';
|
||||||
|
|
||||||
import {
|
|
||||||
setInternetCredentials,
|
|
||||||
resetInternetCredentials,
|
|
||||||
getInternetCredentials,
|
|
||||||
} from './key-store';
|
|
||||||
import { Secp256k1HdWallet } from '@cosmjs/amino';
|
import { Secp256k1HdWallet } from '@cosmjs/amino';
|
||||||
import { AccountData } from '@cosmjs/proto-signing';
|
import { AccountData } from '@cosmjs/proto-signing';
|
||||||
import { stringToPath } from '@cosmjs/crypto';
|
import { stringToPath } from '@cosmjs/crypto';
|
||||||
|
|
||||||
import { Account, NetworksDataState, NetworksFormData } from '../types';
|
import { Account, NetworksDataState, NetworksFormData } from '../types';
|
||||||
import {
|
|
||||||
getHDPath,
|
|
||||||
getPathKey,
|
|
||||||
resetKeyServers,
|
|
||||||
updateAccountIndices,
|
|
||||||
} from './misc';
|
|
||||||
import { COSMOS, EIP155 } from './constants';
|
import { COSMOS, EIP155 } from './constants';
|
||||||
|
import * as eyre from './eyre-client';
|
||||||
|
|
||||||
|
function splitNsChain(nsChain: string): [string, string] {
|
||||||
|
const i = nsChain.indexOf(':');
|
||||||
|
return [nsChain.slice(0, i), nsChain.slice(i + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
const createWallet = async (
|
const createWallet = async (
|
||||||
networksData: NetworksDataState[],
|
networksData: NetworksDataState[],
|
||||||
recoveryPhrase?: string,
|
recoveryPhrase?: string,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const mnemonic = recoveryPhrase ? recoveryPhrase : utils.entropyToMnemonic(utils.randomBytes(16));
|
const mnemonic = recoveryPhrase ? recoveryPhrase : utils.entropyToMnemonic(utils.randomBytes(16));
|
||||||
|
await eyre.createWallet(networksData, mnemonic);
|
||||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
|
||||||
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
|
|
||||||
|
|
||||||
await createWalletFromMnemonic(networksData, hdNode, mnemonic);
|
|
||||||
|
|
||||||
return mnemonic;
|
return mnemonic;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createWalletFromMnemonic = async (
|
const createWalletFromMnemonic = async (
|
||||||
networksData: NetworksDataState[],
|
networksData: NetworksDataState[],
|
||||||
hdNode: HDNode,
|
_hdNode: HDNode,
|
||||||
mnemonic: string
|
mnemonic: string
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
for (const network of networksData) {
|
// HD derivation delegated to agent — hdNode param unused
|
||||||
const hdPath = `m/44'/${network.coinType}'/0'/0/0`;
|
await eyre.createWallet(networksData, mnemonic);
|
||||||
const node = hdNode.derivePath(hdPath);
|
|
||||||
let address;
|
|
||||||
|
|
||||||
switch (network.namespace) {
|
|
||||||
case EIP155:
|
|
||||||
address = node.address;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COSMOS:
|
|
||||||
address = (
|
|
||||||
await getCosmosAccountByHDPath(mnemonic, hdPath, network.addressPrefix)
|
|
||||||
).data.address;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error('Unsupported namespace');
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
setInternetCredentials(
|
|
||||||
`accounts/${network.namespace}:${network.chainId}/0`,
|
|
||||||
'_',
|
|
||||||
accountInfo,
|
|
||||||
),
|
|
||||||
setInternetCredentials(
|
|
||||||
`addAccountCounter/${network.namespace}:${network.chainId}`,
|
|
||||||
'_',
|
|
||||||
'1',
|
|
||||||
),
|
|
||||||
setInternetCredentials(
|
|
||||||
`accountIndices/${network.namespace}:${network.chainId}`,
|
|
||||||
'_',
|
|
||||||
'0',
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addAccount = async (
|
const addAccount = async (
|
||||||
chainId: string,
|
chainId: string,
|
||||||
): Promise<Account | undefined> => {
|
): Promise<Account | undefined> => {
|
||||||
try {
|
try {
|
||||||
let selectedNetworkAccount
|
const networksData = await eyre.getNetworks();
|
||||||
const networksData = await retrieveNetworksData();
|
|
||||||
|
|
||||||
// Add account to all networks and return account for selected network
|
// Add account to all networks (agent handles derivation + counter)
|
||||||
for (const network of networksData) {
|
for (const network of networksData) {
|
||||||
const namespaceChainId = `${network.namespace}:${network.chainId}`;
|
await eyre.addAccount(network.namespace, network.chainId);
|
||||||
const id = await getNextAccountId(namespaceChainId);
|
|
||||||
const hdPath = getHDPath(namespaceChainId, `0'/0/${id}`);
|
|
||||||
const account = await addAccountFromHDPath(hdPath, network);
|
|
||||||
await updateAccountCounter(namespaceChainId, id);
|
|
||||||
|
|
||||||
if (network.chainId === chainId) {
|
|
||||||
selectedNetworkAccount = account;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedNetworkAccount;
|
// Return the new account for the selected network
|
||||||
|
const selectedNetwork = networksData.find(n => n.chainId === chainId);
|
||||||
|
if (!selectedNetwork) return;
|
||||||
|
|
||||||
|
const accounts = await eyre.getAccounts(selectedNetwork.namespace, chainId);
|
||||||
|
return accounts[accounts.length - 1];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating account:', error);
|
console.error('Error creating account:', error);
|
||||||
}
|
}
|
||||||
@@ -117,13 +65,8 @@ const addAccountsForNetwork = async (
|
|||||||
numberOfAccounts: number,
|
numberOfAccounts: number,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const namespaceChainId = `${network.namespace}:${network.chainId}`;
|
|
||||||
|
|
||||||
for (let i = 0; i < numberOfAccounts; i++) {
|
for (let i = 0; i < numberOfAccounts; i++) {
|
||||||
const id = await getNextAccountId(namespaceChainId);
|
await eyre.addAccount(network.namespace, network.chainId);
|
||||||
const hdPath = getHDPath(namespaceChainId, `0'/0/${id}`);
|
|
||||||
await addAccountFromHDPath(hdPath, network);
|
|
||||||
await updateAccountCounter(namespaceChainId, id);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating account:', error);
|
console.error('Error creating account:', error);
|
||||||
@@ -135,26 +78,11 @@ const addAccountFromHDPath = async (
|
|||||||
networkData: NetworksDataState,
|
networkData: NetworksDataState,
|
||||||
): Promise<Account | undefined> => {
|
): Promise<Account | undefined> => {
|
||||||
try {
|
try {
|
||||||
const account = await accountInfoFromHDPath(hdPath, networkData);
|
// Agent derives from stored mnemonic using the given HD path
|
||||||
if (!account) {
|
await eyre.addAccountFromPath(networkData.namespace, networkData.chainId, hdPath);
|
||||||
throw new Error('Error while creating account');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { privKey, pubKey, address } = account;
|
const accounts = await eyre.getAccounts(networkData.namespace, networkData.chainId);
|
||||||
|
return accounts[accounts.length - 1];
|
||||||
const namespaceChainId = `${networkData.namespace}:${networkData.chainId}`;
|
|
||||||
|
|
||||||
const index = (await updateAccountIndices(namespaceChainId)).index;
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
setInternetCredentials(
|
|
||||||
`accounts/${namespaceChainId}/${index}`,
|
|
||||||
'_',
|
|
||||||
`${hdPath},${privKey},${pubKey},${address}`,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { index, pubKey, address, hdPath };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
@@ -163,160 +91,37 @@ const addAccountFromHDPath = async (
|
|||||||
const addNewNetwork = async (
|
const addNewNetwork = async (
|
||||||
newNetworkData: NetworksFormData
|
newNetworkData: NetworksFormData
|
||||||
): Promise<NetworksDataState[]> => {
|
): Promise<NetworksDataState[]> => {
|
||||||
const mnemonicServer = await getInternetCredentials("mnemonicServer");
|
await eyre.addNetwork(newNetworkData);
|
||||||
const mnemonic = mnemonicServer;
|
return eyre.getNetworks();
|
||||||
|
};
|
||||||
if (!mnemonic) {
|
|
||||||
throw new Error("Mnemonic not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
|
||||||
|
|
||||||
const hdPath = `m/44'/${newNetworkData.coinType}'/0'/0/0`;
|
|
||||||
const node = hdNode.derivePath(hdPath);
|
|
||||||
let address;
|
|
||||||
|
|
||||||
switch (newNetworkData.namespace) {
|
|
||||||
case EIP155:
|
|
||||||
address = node.address;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COSMOS:
|
|
||||||
address = (
|
|
||||||
await getCosmosAccountByHDPath(
|
|
||||||
mnemonic,
|
|
||||||
hdPath,
|
|
||||||
newNetworkData.addressPrefix,
|
|
||||||
)
|
|
||||||
).data.address;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error("Unsupported namespace");
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
setInternetCredentials(
|
|
||||||
`accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`,
|
|
||||||
"_",
|
|
||||||
accountInfo,
|
|
||||||
),
|
|
||||||
setInternetCredentials(
|
|
||||||
`addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`,
|
|
||||||
"_",
|
|
||||||
"1",
|
|
||||||
),
|
|
||||||
setInternetCredentials(
|
|
||||||
`accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`,
|
|
||||||
"_",
|
|
||||||
"0",
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const retrievedNetworksData = await storeNetworkData(newNetworkData);
|
|
||||||
|
|
||||||
// Get number of accounts in first network
|
|
||||||
const nextAccountId = await getNextAccountId(
|
|
||||||
`${retrievedNetworksData[0].namespace}:${retrievedNetworksData[0].chainId}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedNetwork = retrievedNetworksData.find(
|
|
||||||
(network) => network.chainId === newNetworkData.chainId,
|
|
||||||
);
|
|
||||||
|
|
||||||
await addAccountsForNetwork(selectedNetwork!, nextAccountId - 1);
|
|
||||||
|
|
||||||
return retrievedNetworksData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storeNetworkData = async (
|
const storeNetworkData = async (
|
||||||
networkData: NetworksFormData,
|
networkData: NetworksFormData,
|
||||||
): Promise<NetworksDataState[]> => {
|
): Promise<NetworksDataState[]> => {
|
||||||
const networks = await getInternetCredentials('networks');
|
await eyre.addNetwork(networkData);
|
||||||
let retrievedNetworks = [];
|
return eyre.getNetworks();
|
||||||
if (networks) {
|
|
||||||
retrievedNetworks = JSON.parse(networks!);
|
|
||||||
}
|
|
||||||
let networkId = 0;
|
|
||||||
if (retrievedNetworks.length > 0) {
|
|
||||||
networkId = retrievedNetworks[retrievedNetworks.length - 1].networkId + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedNetworks: NetworksDataState[] = [
|
|
||||||
...retrievedNetworks,
|
|
||||||
{
|
|
||||||
...networkData,
|
|
||||||
networkId: String(networkId),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await setInternetCredentials(
|
|
||||||
'networks',
|
|
||||||
'_',
|
|
||||||
JSON.stringify(updatedNetworks),
|
|
||||||
);
|
|
||||||
|
|
||||||
return updatedNetworks;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const retrieveNetworksData = async (): Promise<NetworksDataState[]> => {
|
const retrieveNetworksData = async (): Promise<NetworksDataState[]> => {
|
||||||
const networks = await getInternetCredentials('networks');
|
return eyre.getNetworks();
|
||||||
|
|
||||||
if (!networks) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedNetworks: NetworksDataState[] = JSON.parse(networks);
|
|
||||||
|
|
||||||
return parsedNetworks;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const retrieveAccountsForNetwork = async (
|
export const retrieveAccountsForNetwork = async (
|
||||||
namespaceChainId: string,
|
namespaceChainId: string,
|
||||||
accountsIndices: string,
|
_accountsIndices?: string,
|
||||||
): Promise<Account[]> => {
|
): Promise<Account[]> => {
|
||||||
const accountsIndexArray = accountsIndices.split(',');
|
const [ns, chain] = splitNsChain(namespaceChainId);
|
||||||
|
return eyre.getAccounts(ns, chain);
|
||||||
const loadedAccounts = await Promise.all(
|
|
||||||
accountsIndexArray.map(async i => {
|
|
||||||
const { address, path, pubKey } = await getPathKey(
|
|
||||||
namespaceChainId,
|
|
||||||
Number(i),
|
|
||||||
);
|
|
||||||
|
|
||||||
const account: Account = {
|
|
||||||
index: Number(i),
|
|
||||||
pubKey,
|
|
||||||
address,
|
|
||||||
hdPath: path,
|
|
||||||
};
|
|
||||||
|
|
||||||
return account;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return loadedAccounts;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const retrieveAccounts = async (
|
const retrieveAccounts = async (
|
||||||
currentNetworkData: NetworksDataState,
|
currentNetworkData: NetworksDataState,
|
||||||
): Promise<Account[] | undefined> => {
|
): Promise<Account[] | undefined> => {
|
||||||
const accountIndicesServer = await getInternetCredentials(
|
const accounts = await eyre.getAccounts(
|
||||||
`accountIndices/${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
|
currentNetworkData.namespace,
|
||||||
|
currentNetworkData.chainId,
|
||||||
);
|
);
|
||||||
const accountIndices = accountIndicesServer;
|
return accounts.length > 0 ? accounts : undefined;
|
||||||
if (!accountIndices) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadedAccounts = await retrieveAccountsForNetwork(
|
|
||||||
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
|
|
||||||
accountIndices,
|
|
||||||
)
|
|
||||||
|
|
||||||
return loadedAccounts;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const retrieveSingleAccount = async (
|
const retrieveSingleAccount = async (
|
||||||
@@ -324,37 +129,13 @@ const retrieveSingleAccount = async (
|
|||||||
chainId: string,
|
chainId: string,
|
||||||
address: string,
|
address: string,
|
||||||
) => {
|
) => {
|
||||||
let loadedAccounts;
|
const accounts = await eyre.getAccounts(namespace, chainId);
|
||||||
|
return accounts.find(account => account.address === address);
|
||||||
const accountIndicesServer = await getInternetCredentials(
|
|
||||||
`accountIndices/${namespace}:${chainId}`,
|
|
||||||
);
|
|
||||||
const accountIndices = accountIndicesServer;
|
|
||||||
|
|
||||||
if (!accountIndices) {
|
|
||||||
throw new Error('Indices for given chain not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedAccounts = await retrieveAccountsForNetwork(
|
|
||||||
`${namespace}:${chainId}`,
|
|
||||||
accountIndices,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!loadedAccounts) {
|
|
||||||
throw new Error('Accounts for given chain not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return loadedAccounts.find(account => account.address === address);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetWallet = async () => {
|
const resetWallet = async () => {
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await eyre.resetWallet();
|
||||||
resetInternetCredentials('mnemonicServer'),
|
|
||||||
resetKeyServers(EIP155),
|
|
||||||
resetKeyServers(COSMOS),
|
|
||||||
setInternetCredentials('networks', '_', JSON.stringify([])),
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error resetting wallet:', error);
|
console.error('Error resetting wallet:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -367,12 +148,13 @@ const accountInfoFromHDPath = async (
|
|||||||
): Promise<
|
): Promise<
|
||||||
{ privKey: string; pubKey: string; address: string } | undefined
|
{ privKey: string; pubKey: string; address: string } | undefined
|
||||||
> => {
|
> => {
|
||||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
// Phase 1: still derives in browser for API compat.
|
||||||
if (!mnemonicStore) {
|
// Phase 2: this moves to the agent entirely.
|
||||||
|
const mnemonic = await eyre.getMnemonic();
|
||||||
|
if (!mnemonic) {
|
||||||
throw new Error('Mnemonic not found!');
|
throw new Error('Mnemonic not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const mnemonic = mnemonicStore;
|
|
||||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||||
const node = hdNode.derivePath(hdPath);
|
const node = hdNode.derivePath(hdPath);
|
||||||
|
|
||||||
@@ -397,36 +179,15 @@ const accountInfoFromHDPath = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getNextAccountId = async (namespaceChainId: string): Promise<number> => {
|
const getNextAccountId = async (namespaceChainId: string): Promise<number> => {
|
||||||
const idStore = await getInternetCredentials(
|
const [ns, chain] = splitNsChain(namespaceChainId);
|
||||||
`addAccountCounter/${namespaceChainId}`,
|
return eyre.getNextAccountId(ns, chain);
|
||||||
);
|
|
||||||
if (!idStore) {
|
|
||||||
throw new Error('Account id not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountCounter = idStore;
|
|
||||||
const nextCounter = Number(accountCounter);
|
|
||||||
return nextCounter;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAccountCounter = async (
|
const updateAccountCounter = async (
|
||||||
namespaceChainId: string,
|
_namespaceChainId: string,
|
||||||
id: number,
|
_id: number,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const idStore = await getInternetCredentials(
|
// Agent manages account counter atomically via addAccount poke.
|
||||||
`addAccountCounter/${namespaceChainId}`,
|
|
||||||
);
|
|
||||||
if (!idStore) {
|
|
||||||
throw new Error('Account id not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedCounter = String(id + 1);
|
|
||||||
await resetInternetCredentials(`addAccountCounter/${namespaceChainId}`);
|
|
||||||
await setInternetCredentials(
|
|
||||||
`addAccountCounter/${namespaceChainId}`,
|
|
||||||
'_',
|
|
||||||
updatedCounter,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCosmosAccountByHDPath = async (
|
const getCosmosAccountByHDPath = async (
|
||||||
@@ -448,23 +209,13 @@ const getCosmosAccountByHDPath = async (
|
|||||||
const checkNetworkForChainID = async (
|
const checkNetworkForChainID = async (
|
||||||
chainId: string,
|
chainId: string,
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const networks = await getInternetCredentials('networks');
|
const networks = await eyre.getNetworks();
|
||||||
|
return networks.some((network) => network.chainId === chainId);
|
||||||
if (!networks) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const networksData: NetworksFormData[] = JSON.parse(networks);
|
|
||||||
|
|
||||||
return networksData.some((network) => network.chainId === chainId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isWalletCreated = async (
|
const isWalletCreated = async (
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const mnemonicServer = await getInternetCredentials("mnemonicServer");
|
return eyre.walletExists();
|
||||||
const mnemonic = mnemonicServer;
|
|
||||||
|
|
||||||
return mnemonic !== null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -0,0 +1,441 @@
|
|||||||
|
/**
|
||||||
|
* Eyre HTTP client for communicating with the %zenith agent.
|
||||||
|
*
|
||||||
|
* Replaces localStorage (key-store.ts) with Urbit ship storage via:
|
||||||
|
* - Scry: GET /~/scry/zenith/{path}.json
|
||||||
|
* - Poke: PUT /~/channel/{uid} with channel API
|
||||||
|
*
|
||||||
|
* Three layers:
|
||||||
|
* 1. Base helpers — scryGet(), pokeAction()
|
||||||
|
* 2. Typed API — getNetworks(), createWallet(), etc.
|
||||||
|
* 3. Compatibility — getWalletData(), setWalletData(), deleteWalletData()
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Account, NetworksDataState, NetworksFormData } from '../types';
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Types
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
export interface AccountWithKeys extends Account {
|
||||||
|
privKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PendingPoke {
|
||||||
|
resolve: () => void;
|
||||||
|
reject: (err: Error) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Channel state
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const APP_NAME = 'zenith';
|
||||||
|
const POKE_TIMEOUT_MS = 30_000;
|
||||||
|
|
||||||
|
let channelUid: string | null = null;
|
||||||
|
let eventSource: EventSource | null = null;
|
||||||
|
let messageId = 0;
|
||||||
|
let cachedShipName: string | null = null;
|
||||||
|
const pendingPokes = new Map<number, PendingPoke>();
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 1. Base helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover the ship name. Uses /~/host (same as @urbit/http-api),
|
||||||
|
* with fallback to the Eyre auth cookie.
|
||||||
|
*/
|
||||||
|
async function getShipName(): Promise<string> {
|
||||||
|
if (cachedShipName) return cachedShipName;
|
||||||
|
|
||||||
|
const res = await fetch('/~/host', { credentials: 'include' });
|
||||||
|
if (res.ok) {
|
||||||
|
cachedShipName = await res.text();
|
||||||
|
return cachedShipName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = document.cookie.match(/urbauth-(~[a-z-]+)=/);
|
||||||
|
if (match) {
|
||||||
|
cachedShipName = match[1];
|
||||||
|
return cachedShipName;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Cannot determine ship name — not authenticated with Eyre');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannelUrl(): string {
|
||||||
|
if (!channelUid) {
|
||||||
|
channelUid = `wallet-${Date.now()}`;
|
||||||
|
}
|
||||||
|
return `/~/channel/${channelUid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the SSE event source for receiving poke acks.
|
||||||
|
* Eyre buffers events until the client connects, so it's safe to open
|
||||||
|
* after the first PUT.
|
||||||
|
*/
|
||||||
|
function openEventSource(): void {
|
||||||
|
if (eventSource) return;
|
||||||
|
|
||||||
|
eventSource = new EventSource(getChannelUrl(), {
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
eventSource.addEventListener('message', (ev: MessageEvent) => {
|
||||||
|
const data = JSON.parse(ev.data);
|
||||||
|
|
||||||
|
if (data.response === 'poke') {
|
||||||
|
const pending = pendingPokes.get(data.id);
|
||||||
|
if (pending) {
|
||||||
|
if (data.ok === 'ok') {
|
||||||
|
pending.resolve();
|
||||||
|
} else {
|
||||||
|
pending.reject(new Error(data.err ?? 'Poke rejected by agent'));
|
||||||
|
}
|
||||||
|
pendingPokes.delete(data.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acknowledge every SSE event to keep the channel alive
|
||||||
|
const ackId = ++messageId;
|
||||||
|
fetch(getChannelUrl(), {
|
||||||
|
method: 'PUT',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify([{
|
||||||
|
id: ackId,
|
||||||
|
action: 'ack',
|
||||||
|
'event-id': Number(ev.lastEventId),
|
||||||
|
}]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
eventSource.addEventListener('error', () => {
|
||||||
|
for (const [, pending] of pendingPokes) {
|
||||||
|
pending.reject(new Error('Eyre channel disconnected'));
|
||||||
|
}
|
||||||
|
pendingPokes.clear();
|
||||||
|
eventSource?.close();
|
||||||
|
eventSource = null;
|
||||||
|
channelUid = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scry the %zenith agent. Returns parsed JSON, or null on 404.
|
||||||
|
*
|
||||||
|
* @param path - Scry path starting with /, e.g. '/wallet/networks'
|
||||||
|
*/
|
||||||
|
async function scryGet<T>(path: string): Promise<T | null> {
|
||||||
|
const res = await fetch(`/~/scry/${APP_NAME}${path}.json`, {
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
if (res.status === 404) return null;
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Scry ${path} failed: ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poke the %zenith agent and wait for the ack/nack.
|
||||||
|
*
|
||||||
|
* @param mark - Poke mark, e.g. 'json' or 'wallet-action'
|
||||||
|
* @param json - Poke payload
|
||||||
|
*/
|
||||||
|
async function pokeAction(mark: string, json: unknown): Promise<void> {
|
||||||
|
const shipName = await getShipName();
|
||||||
|
const id = ++messageId;
|
||||||
|
|
||||||
|
const ackPromise = new Promise<void>((resolve, reject) => {
|
||||||
|
pendingPokes.set(id, { resolve, reject });
|
||||||
|
setTimeout(() => {
|
||||||
|
if (pendingPokes.has(id)) {
|
||||||
|
pendingPokes.delete(id);
|
||||||
|
reject(new Error(`Poke timed out after ${POKE_TIMEOUT_MS}ms`));
|
||||||
|
}
|
||||||
|
}, POKE_TIMEOUT_MS);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetch(getChannelUrl(), {
|
||||||
|
method: 'PUT',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify([{
|
||||||
|
id,
|
||||||
|
action: 'poke',
|
||||||
|
ship: shipName,
|
||||||
|
app: APP_NAME,
|
||||||
|
mark,
|
||||||
|
json,
|
||||||
|
}]),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
pendingPokes.delete(id);
|
||||||
|
throw new Error(`Channel PUT failed: ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open SSE to receive ack (no-op if already open).
|
||||||
|
// Eyre buffers the ack until we connect.
|
||||||
|
openEventSource();
|
||||||
|
|
||||||
|
return ackPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 2. Typed API — Reads (scry)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
async function walletExists(): Promise<boolean> {
|
||||||
|
const data = await scryGet<{ exists: boolean }>('/wallet/exists');
|
||||||
|
return data?.exists ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMnemonic(): Promise<string | null> {
|
||||||
|
const data = await scryGet<{ mnemonic: string }>('/wallet/mnemonic');
|
||||||
|
return data?.mnemonic ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNetworks(): Promise<NetworksDataState[]> {
|
||||||
|
return (await scryGet<NetworksDataState[]>('/wallet/networks')) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAccounts(
|
||||||
|
namespace: string,
|
||||||
|
chainId: string,
|
||||||
|
): Promise<Account[]> {
|
||||||
|
return (
|
||||||
|
(await scryGet<Account[]>(`/wallet/accounts/${namespace}:${chainId}`)) ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAccount(
|
||||||
|
namespace: string,
|
||||||
|
chainId: string,
|
||||||
|
index: number,
|
||||||
|
): Promise<AccountWithKeys | null> {
|
||||||
|
return scryGet<AccountWithKeys>(
|
||||||
|
`/wallet/account/${namespace}:${chainId}/${index}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNextAccountId(
|
||||||
|
namespace: string,
|
||||||
|
chainId: string,
|
||||||
|
): Promise<number> {
|
||||||
|
const data = await scryGet<{ id: number }>(
|
||||||
|
`/wallet/next-account-id/${namespace}:${chainId}`,
|
||||||
|
);
|
||||||
|
return data?.id ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 2. Typed API — Writes (poke)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
async function createWallet(
|
||||||
|
networks: NetworksFormData[],
|
||||||
|
mnemonic?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const payload: Record<string, unknown> = {
|
||||||
|
action: 'create-wallet',
|
||||||
|
networks,
|
||||||
|
};
|
||||||
|
if (mnemonic) {
|
||||||
|
payload.mnemonic = mnemonic;
|
||||||
|
}
|
||||||
|
return pokeAction('json', payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addAccount(
|
||||||
|
namespace: string,
|
||||||
|
chainId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return pokeAction('json', {
|
||||||
|
action: 'add-account',
|
||||||
|
namespace,
|
||||||
|
'chain-id': chainId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateNetworks(
|
||||||
|
networks: NetworksDataState[],
|
||||||
|
): Promise<void> {
|
||||||
|
return pokeAction('json', {
|
||||||
|
action: 'update-networks',
|
||||||
|
networks,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addNetwork(network: NetworksFormData): Promise<void> {
|
||||||
|
return pokeAction('json', {
|
||||||
|
action: 'add-network',
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addAccountFromPath(
|
||||||
|
namespace: string,
|
||||||
|
chainId: string,
|
||||||
|
hdPath: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return pokeAction('json', {
|
||||||
|
action: 'add-account-from-path',
|
||||||
|
namespace,
|
||||||
|
'chain-id': chainId,
|
||||||
|
'hd-path': hdPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetWallet(): Promise<void> {
|
||||||
|
return pokeAction('json', { action: 'reset-wallet' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 3. Drop-in compatibility layer
|
||||||
|
//
|
||||||
|
// Maps the old localStorage key patterns to the typed API so
|
||||||
|
// existing callers can migrate incrementally in Phase 2.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const ACCOUNTS_RE = /^accounts\/([^/]+)\/(\d+)$/;
|
||||||
|
const INDICES_RE = /^accountIndices\/(.+)$/;
|
||||||
|
const COUNTER_RE = /^addAccountCounter\/(.+)$/;
|
||||||
|
|
||||||
|
function splitNsChain(nsChain: string): [string, string] {
|
||||||
|
const i = nsChain.indexOf(':');
|
||||||
|
return [nsChain.slice(0, i), nsChain.slice(i + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop-in replacement for getInternetCredentials(key).
|
||||||
|
* Returns a string matching the old localStorage value format, or null.
|
||||||
|
*/
|
||||||
|
async function getWalletData(key: string): Promise<string | null> {
|
||||||
|
if (key === 'mnemonicServer') {
|
||||||
|
return getMnemonic();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'networks') {
|
||||||
|
const networks = await getNetworks();
|
||||||
|
return networks.length > 0 ? JSON.stringify(networks) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountMatch = key.match(ACCOUNTS_RE);
|
||||||
|
if (accountMatch) {
|
||||||
|
const [ns, chain] = splitNsChain(accountMatch[1]);
|
||||||
|
const account = await getAccount(ns, chain, Number(accountMatch[2]));
|
||||||
|
if (!account) return null;
|
||||||
|
return `${account.hdPath},${account.privKey},${account.pubKey},${account.address}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indicesMatch = key.match(INDICES_RE);
|
||||||
|
if (indicesMatch) {
|
||||||
|
const [ns, chain] = splitNsChain(indicesMatch[1]);
|
||||||
|
const accounts = await getAccounts(ns, chain);
|
||||||
|
if (accounts.length === 0) return null;
|
||||||
|
return accounts.map((a) => a.index).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
const counterMatch = key.match(COUNTER_RE);
|
||||||
|
if (counterMatch) {
|
||||||
|
const [ns, chain] = splitNsChain(counterMatch[1]);
|
||||||
|
const id = await getNextAccountId(ns, chain);
|
||||||
|
return String(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop-in replacement for setInternetCredentials(key, _, value).
|
||||||
|
* Only the `networks` key supports direct writes — account mutations
|
||||||
|
* should use the typed API (createWallet, addAccount).
|
||||||
|
*/
|
||||||
|
async function setWalletData(key: string, value: string): Promise<void> {
|
||||||
|
if (key === 'networks') {
|
||||||
|
const networks: NetworksDataState[] = JSON.parse(value);
|
||||||
|
return updateNetworks(networks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'mnemonicServer') {
|
||||||
|
throw new Error(
|
||||||
|
'Cannot set mnemonic directly — use createWallet() instead',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ACCOUNTS_RE.test(key) || INDICES_RE.test(key) || COUNTER_RE.test(key)) {
|
||||||
|
throw new Error(
|
||||||
|
`Direct write to '${key}' not supported — use createWallet() or addAccount()`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown wallet data key: ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop-in replacement for resetInternetCredentials(key).
|
||||||
|
* Individual account key deletion is a no-op — the agent manages
|
||||||
|
* account lifecycle atomically via resetWallet().
|
||||||
|
*/
|
||||||
|
async function deleteWalletData(key: string): Promise<void> {
|
||||||
|
if (key === 'mnemonicServer') {
|
||||||
|
return resetWallet();
|
||||||
|
}
|
||||||
|
// Account-related keys are managed atomically by the agent.
|
||||||
|
// Individual deletes are no-ops since resetWallet() handles cleanup.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 4. Channel lifecycle
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
function closeChannel(): void {
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close();
|
||||||
|
eventSource = null;
|
||||||
|
}
|
||||||
|
for (const [, pending] of pendingPokes) {
|
||||||
|
pending.reject(new Error('Channel closed'));
|
||||||
|
}
|
||||||
|
pendingPokes.clear();
|
||||||
|
channelUid = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Exports
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Base helpers
|
||||||
|
scryGet,
|
||||||
|
pokeAction,
|
||||||
|
|
||||||
|
// Typed API — reads
|
||||||
|
walletExists,
|
||||||
|
getMnemonic,
|
||||||
|
getNetworks,
|
||||||
|
getAccounts,
|
||||||
|
getAccount,
|
||||||
|
getNextAccountId,
|
||||||
|
|
||||||
|
// Typed API — writes
|
||||||
|
createWallet,
|
||||||
|
addAccount,
|
||||||
|
updateNetworks,
|
||||||
|
addNetwork,
|
||||||
|
addAccountFromPath,
|
||||||
|
resetWallet,
|
||||||
|
|
||||||
|
// Compatibility layer (Phase 1 drop-in for key-store.ts)
|
||||||
|
getWalletData,
|
||||||
|
setWalletData,
|
||||||
|
deleteWalletData,
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
closeChannel,
|
||||||
|
};
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @deprecated Use eyre-client.ts instead. This module will be removed in Phase 2.
|
||||||
|
* All consumers have been migrated to eyre-client.ts typed API.
|
||||||
|
*/
|
||||||
|
|
||||||
const setInternetCredentials = (name:string, username:string, password:string) => {
|
const setInternetCredentials = (name:string, username:string, password:string) => {
|
||||||
localStorage.setItem(name, password);
|
localStorage.setItem(name, password);
|
||||||
};
|
};
|
||||||
|
|||||||
+39
-79
@@ -9,20 +9,23 @@ import { stringToPath } from '@cosmjs/crypto';
|
|||||||
import '@ethersproject/shims';
|
import '@ethersproject/shims';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getInternetCredentials,
|
getMnemonic as eyreGetMnemonic,
|
||||||
resetInternetCredentials,
|
getAccounts,
|
||||||
setInternetCredentials,
|
getAccount,
|
||||||
} from './key-store';
|
} from './eyre-client';
|
||||||
import { EIP155 } from './constants';
|
import { EIP155 } from './constants';
|
||||||
import { NetworksDataState } from '../types';
|
|
||||||
|
function splitNsChain(namespaceChainId: string): [string, string] {
|
||||||
|
const i = namespaceChainId.indexOf(':');
|
||||||
|
return [namespaceChainId.slice(0, i), namespaceChainId.slice(i + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
const getMnemonic = async (): Promise<string> => {
|
const getMnemonic = async (): Promise<string> => {
|
||||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
const mnemonic = await eyreGetMnemonic();
|
||||||
if (!mnemonicStore) {
|
if (!mnemonic) {
|
||||||
throw new Error('Mnemonic not found!');
|
throw new Error('Mnemonic not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const mnemonic = mnemonicStore;
|
|
||||||
return mnemonic;
|
return mnemonic;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,22 +56,19 @@ const getPathKey = async (
|
|||||||
pubKey: string;
|
pubKey: string;
|
||||||
address: string;
|
address: string;
|
||||||
}> => {
|
}> => {
|
||||||
const pathKeyStore = await getInternetCredentials(
|
const [ns, chain] = splitNsChain(namespaceChainId);
|
||||||
`accounts/${namespaceChainId}/${accountId}`,
|
const account = await getAccount(ns, chain, accountId);
|
||||||
);
|
|
||||||
|
|
||||||
if (!pathKeyStore) {
|
if (!account) {
|
||||||
throw new Error('Error while fetching counter');
|
throw new Error('Error while fetching account');
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathKeyVal = pathKeyStore;
|
return {
|
||||||
const pathkey = pathKeyVal.split(',');
|
path: account.hdPath,
|
||||||
const path = pathkey[0];
|
privKey: account.privKey,
|
||||||
const privKey = pathkey[1];
|
pubKey: account.pubKey,
|
||||||
const pubKey = pathkey[2];
|
address: account.address,
|
||||||
const address = pathkey[3];
|
};
|
||||||
|
|
||||||
return { path, privKey, pubKey, address };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAccountIndices = async (
|
const getAccountIndices = async (
|
||||||
@@ -78,75 +78,35 @@ const getAccountIndices = async (
|
|||||||
indices: number[];
|
indices: number[];
|
||||||
index: number;
|
index: number;
|
||||||
}> => {
|
}> => {
|
||||||
const counterStore = await getInternetCredentials(
|
const [ns, chain] = splitNsChain(namespaceChainId);
|
||||||
`accountIndices/${namespaceChainId}`,
|
const accounts = await getAccounts(ns, chain);
|
||||||
);
|
|
||||||
|
|
||||||
if (!counterStore) {
|
if (accounts.length === 0) {
|
||||||
throw new Error('Error while fetching counter');
|
throw new Error('Error while fetching accounts');
|
||||||
}
|
}
|
||||||
|
|
||||||
let accountIndices = counterStore;
|
const indices = accounts.map(a => a.index);
|
||||||
const indices = accountIndices.split(',').map(Number);
|
const maxIndex = Math.max(...indices);
|
||||||
const index = indices[indices.length - 1] + 1;
|
|
||||||
|
|
||||||
return { accountIndices, indices, index };
|
return {
|
||||||
|
accountIndices: indices.join(','),
|
||||||
|
indices,
|
||||||
|
index: maxIndex + 1,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAccountIndices = async (
|
const updateAccountIndices = async (
|
||||||
namespaceChainId: string,
|
namespaceChainId: string,
|
||||||
): Promise<{ accountIndices: string; index: number }> => {
|
): Promise<{ accountIndices: string; index: number }> => {
|
||||||
const accountIndicesData = await getAccountIndices(namespaceChainId);
|
// Agent manages account indices atomically via addAccount poke.
|
||||||
const accountIndices = accountIndicesData.accountIndices;
|
// This reads current state for callers that still expect the old return shape.
|
||||||
const index = accountIndicesData.index;
|
const data = await getAccountIndices(namespaceChainId);
|
||||||
const updatedAccountIndices = `${accountIndices},${index.toString()}`;
|
return { accountIndices: data.accountIndices, index: data.index };
|
||||||
|
|
||||||
await resetInternetCredentials(`accountIndices/${namespaceChainId}`);
|
|
||||||
await setInternetCredentials(
|
|
||||||
`accountIndices/${namespaceChainId}`,
|
|
||||||
'_',
|
|
||||||
updatedAccountIndices,
|
|
||||||
);
|
|
||||||
|
|
||||||
return { accountIndices: updatedAccountIndices, index };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetKeyServers = async (namespace: string) => {
|
const resetKeyServers = async (_namespace: string) => {
|
||||||
const networksServer = await getInternetCredentials('networks');
|
// Agent manages account lifecycle atomically via resetWallet poke.
|
||||||
if (!networksServer) {
|
// Individual namespace cleanup is a no-op — resetWallet handles it all.
|
||||||
throw new Error('Networks not found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const networksData: NetworksDataState[] = JSON.parse(networksServer);
|
|
||||||
const filteredNetworks = networksData.filter(
|
|
||||||
network => network.namespace === namespace,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filteredNetworks.length === 0) {
|
|
||||||
throw new Error(`No networks found for namespace ${namespace}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredNetworks.forEach(async network => {
|
|
||||||
const { chainId } = network;
|
|
||||||
const namespaceChainId = `${namespace}:${chainId}`;
|
|
||||||
|
|
||||||
const idStore = await getInternetCredentials(
|
|
||||||
`accountIndices/${namespaceChainId}`,
|
|
||||||
);
|
|
||||||
if (!idStore) {
|
|
||||||
throw new Error(`Account indices not found for ${namespaceChainId}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountIds = idStore;
|
|
||||||
const ids = accountIds.split(',').map(Number);
|
|
||||||
const latestId = Math.max(...ids);
|
|
||||||
|
|
||||||
for (let i = 0; i <= latestId; i++) {
|
|
||||||
await resetInternetCredentials(`accounts/${namespaceChainId}/${i}`);
|
|
||||||
}
|
|
||||||
await resetInternetCredentials(`addAccountCounter/${namespaceChainId}`);
|
|
||||||
await resetInternetCredentials(`accountIndices/${namespaceChainId}`);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendMessage = (
|
const sendMessage = (
|
||||||
|
|||||||
@@ -38,5 +38,5 @@ export const COSMOS_SIGNING_METHODS = {
|
|||||||
export const COSMOS_METHODS = {
|
export const COSMOS_METHODS = {
|
||||||
...COSMOS_SIGNING_METHODS,
|
...COSMOS_SIGNING_METHODS,
|
||||||
COSMOS_SEND_TOKENS: 'cosmos_sendTokens', // Added for pay.laconic.com
|
COSMOS_SEND_TOKENS: 'cosmos_sendTokens', // Added for pay.laconic.com
|
||||||
COSMOS_SEND_TRANSACTION: 'cosmos_sendTransaction', // Added for testnet onboarding app
|
COSMOS_SEND_TRANSACTION: 'cosmos_sendTransaction', // Added for onboarding app
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ services:
|
|||||||
image: cerc/zenith-wallet-web:local
|
image: cerc/zenith-wallet-web:local
|
||||||
environment:
|
environment:
|
||||||
CERC_SCRIPT_DEBUG: ${CERC_SCRIPT_DEBUG}
|
CERC_SCRIPT_DEBUG: ${CERC_SCRIPT_DEBUG}
|
||||||
WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}
|
|
||||||
WALLET_CONNECT_VERIFY_CODE: ${WALLET_CONNECT_VERIFY_CODE}
|
WALLET_CONNECT_VERIFY_CODE: ${WALLET_CONNECT_VERIFY_CODE}
|
||||||
CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE:-0.025}
|
CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE:-0.025}
|
||||||
CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT:-2}
|
CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT:-2}
|
||||||
|
|||||||
@@ -6,19 +6,17 @@ if [ -n "$CERC_SCRIPT_DEBUG" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Using the following env variables:"
|
echo "Using the following env variables:"
|
||||||
echo "WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}"
|
|
||||||
echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}"
|
echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}"
|
||||||
echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}"
|
echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}"
|
||||||
echo "CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}"
|
echo "CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}"
|
||||||
|
|
||||||
# Build with required env
|
# Build with required env
|
||||||
export REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_ID
|
|
||||||
export REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE
|
export REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE
|
||||||
export REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT
|
export REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT
|
||||||
export REACT_APP_ALLOWED_URLS=$CERC_ALLOWED_URLS
|
export REACT_APP_ALLOWED_URLS=$CERC_ALLOWED_URLS
|
||||||
|
|
||||||
# Set env variables in build
|
# Set env variables in build
|
||||||
yarn set-env
|
import-meta-env -x /app/.env.example -p /app/build/index.html
|
||||||
|
|
||||||
# Define the directory and file path
|
# Define the directory and file path
|
||||||
FILE_PATH="/app/build/.well-known/walletconnect.txt"
|
FILE_PATH="/app/build/.well-known/walletconnect.txt"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
# Originally from: https://github.com/devcontainers/images/blob/main/src/javascript-node/.devcontainer/Dockerfile
|
# Originally from: https://github.com/devcontainers/images/blob/main/src/javascript-node/.devcontainer/Dockerfile
|
||||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||||
ARG VARIANT=22-bullseye
|
ARG VARIANT=22-bullseye
|
||||||
FROM node:${VARIANT}
|
|
||||||
|
# Build stage
|
||||||
|
FROM node:${VARIANT} AS builder
|
||||||
|
|
||||||
ARG USERNAME=node
|
ARG USERNAME=node
|
||||||
ARG NPM_GLOBAL=/usr/local/share/npm-global
|
ARG NPM_GLOBAL=/usr/local/share/npm-global
|
||||||
@@ -26,18 +28,30 @@ RUN \
|
|||||||
&& su ${USERNAME} -c "umask 0002 && npm install -g eslint" \
|
&& su ${USERNAME} -c "umask 0002 && npm install -g eslint" \
|
||||||
&& npm cache clean --force > /dev/null 2>&1
|
&& npm cache clean --force > /dev/null 2>&1
|
||||||
|
|
||||||
# Install additional OS packages.
|
|
||||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|
||||||
&& apt-get -y install --no-install-recommends jq bash netcat
|
|
||||||
|
|
||||||
RUN mkdir -p /scripts
|
|
||||||
|
|
||||||
# Install simple web server for now (use nginx perhaps later)
|
|
||||||
RUN yarn global add serve
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN yarn install && yarn build
|
RUN yarn install && yarn build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:${VARIANT}-slim AS production
|
||||||
|
|
||||||
|
# Install additional OS packages for debugging
|
||||||
|
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
|
&& apt-get -y install --no-install-recommends bash netcat \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir -p /scripts
|
||||||
|
|
||||||
|
# Install serve package globally
|
||||||
|
# Install import-meta-env for setting environment variables (check package.json for version)
|
||||||
|
RUN yarn global add serve@^14.2.4 @import-meta-env/cli@^0.7.3
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy built application and env example from builder stage
|
||||||
|
COPY --from=builder /app/build ./build
|
||||||
|
COPY --from=builder /app/.env.example ./.env.example
|
||||||
|
|
||||||
# Expose port for http
|
# Expose port for http
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
:~ title+'Zenith Wallet'
|
||||||
|
info+'Zenith blockchain wallet with WalletConnect support.'
|
||||||
|
color+0x4f.46e5
|
||||||
|
base+'zenith-wallet'
|
||||||
|
glob-http+['REPLACE_WITH_GLOB_URL' REPLACE_WITH_GLOB_HASH]
|
||||||
|
version+[0 0 1]
|
||||||
|
website+'https://git.vdb.to/LaconicNetwork/zenith-wallet-web'
|
||||||
|
license+'MIT'
|
||||||
|
==
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
:: mark for .ico icon files
|
||||||
|
::
|
||||||
|
|_ dat=octs
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ mime [/image/x-icon dat]
|
||||||
|
--
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ mime |=([p=mite q=octs] q)
|
||||||
|
++ noun |=(=noun ;;(octs noun))
|
||||||
|
--
|
||||||
|
++ grad %mime
|
||||||
|
--
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
:: mark for .map files (source maps)
|
||||||
|
::
|
||||||
|
|_ dat=octs
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ mime [/application/json dat]
|
||||||
|
--
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ mime |=([p=mite q=octs] q)
|
||||||
|
++ noun |=(=noun ;;(octs noun))
|
||||||
|
--
|
||||||
|
++ grad %mime
|
||||||
|
--
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
:: mark for .ttf font files
|
||||||
|
::
|
||||||
|
|_ dat=octs
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ mime [/font/ttf dat]
|
||||||
|
--
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ mime |=([p=mite q=octs] q)
|
||||||
|
++ noun |=(=noun ;;(octs noun))
|
||||||
|
--
|
||||||
|
++ grad %mime
|
||||||
|
--
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
:: mark for .woff font files
|
||||||
|
::
|
||||||
|
|_ dat=octs
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ mime [/font/woff dat]
|
||||||
|
--
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ mime |=([p=mite q=octs] q)
|
||||||
|
++ noun |=(=noun ;;(octs noun))
|
||||||
|
--
|
||||||
|
++ grad %mime
|
||||||
|
--
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
:: mark for .woff2 font files
|
||||||
|
::
|
||||||
|
|_ dat=octs
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ mime [/font/woff2 dat]
|
||||||
|
--
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ mime |=([p=mite q=octs] q)
|
||||||
|
++ noun |=(=noun ;;(octs noun))
|
||||||
|
--
|
||||||
|
++ grad %mime
|
||||||
|
--
|
||||||
@@ -1465,7 +1465,7 @@
|
|||||||
"@cosmjs/math" "0.28.13"
|
"@cosmjs/math" "0.28.13"
|
||||||
"@cosmjs/utils" "0.28.13"
|
"@cosmjs/utils" "0.28.13"
|
||||||
|
|
||||||
"@cosmjs/amino@^0.32.3", "@cosmjs/amino@^0.32.4":
|
"@cosmjs/amino@^0.32.4":
|
||||||
version "0.32.4"
|
version "0.32.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.32.4.tgz#3908946c0394e6d431694c8992c5147079a1c860"
|
resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.32.4.tgz#3908946c0394e6d431694c8992c5147079a1c860"
|
||||||
integrity sha512-zKYOt6hPy8obIFtLie/xtygCkH9ZROiQ12UHfKsOkWaZfPQUvVbtgmu6R4Kn1tFLI/SRkw7eqhaogmW/3NYu/Q==
|
integrity sha512-zKYOt6hPy8obIFtLie/xtygCkH9ZROiQ12UHfKsOkWaZfPQUvVbtgmu6R4Kn1tFLI/SRkw7eqhaogmW/3NYu/Q==
|
||||||
@@ -1475,6 +1475,16 @@
|
|||||||
"@cosmjs/math" "^0.32.4"
|
"@cosmjs/math" "^0.32.4"
|
||||||
"@cosmjs/utils" "^0.32.4"
|
"@cosmjs/utils" "^0.32.4"
|
||||||
|
|
||||||
|
"@cosmjs/amino@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.33.1.tgz#0d4957b2e755af8392627c0c0f72bee129dcdcf3"
|
||||||
|
integrity sha512-WfWiBf2EbIWpwKG9AOcsIIkR717SY+JdlXM/SL/bI66BdrhniAF+/ZNis9Vo9HF6lP2UU5XrSmFA4snAvEgdrg==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/crypto" "^0.33.1"
|
||||||
|
"@cosmjs/encoding" "^0.33.1"
|
||||||
|
"@cosmjs/math" "^0.33.1"
|
||||||
|
"@cosmjs/utils" "^0.33.1"
|
||||||
|
|
||||||
"@cosmjs/crypto@0.27.1":
|
"@cosmjs/crypto@0.27.1":
|
||||||
version "0.27.1"
|
version "0.27.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.27.1.tgz#271c853089a3baf3acd6cf0b2122fd49f8815743"
|
resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.27.1.tgz#271c853089a3baf3acd6cf0b2122fd49f8815743"
|
||||||
@@ -1504,7 +1514,7 @@
|
|||||||
elliptic "^6.5.3"
|
elliptic "^6.5.3"
|
||||||
libsodium-wrappers "^0.7.6"
|
libsodium-wrappers "^0.7.6"
|
||||||
|
|
||||||
"@cosmjs/crypto@^0.32.3", "@cosmjs/crypto@^0.32.4":
|
"@cosmjs/crypto@^0.32.4":
|
||||||
version "0.32.4"
|
version "0.32.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.32.4.tgz#5d29633b661eaf092ddb3e7ea6299cfd6f4507a2"
|
resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.32.4.tgz#5d29633b661eaf092ddb3e7ea6299cfd6f4507a2"
|
||||||
integrity sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==
|
integrity sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==
|
||||||
@@ -1517,6 +1527,19 @@
|
|||||||
elliptic "^6.5.4"
|
elliptic "^6.5.4"
|
||||||
libsodium-wrappers-sumo "^0.7.11"
|
libsodium-wrappers-sumo "^0.7.11"
|
||||||
|
|
||||||
|
"@cosmjs/crypto@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.33.1.tgz#761b1623e4abe8af4cbf7ca92639561314f04c3b"
|
||||||
|
integrity sha512-U4kGIj/SNBzlb2FGgA0sMR0MapVgJUg8N+oIAiN5+vl4GZ3aefmoL1RDyTrFS/7HrB+M+MtHsxC0tvEu4ic/zA==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/encoding" "^0.33.1"
|
||||||
|
"@cosmjs/math" "^0.33.1"
|
||||||
|
"@cosmjs/utils" "^0.33.1"
|
||||||
|
"@noble/hashes" "^1"
|
||||||
|
bn.js "^5.2.0"
|
||||||
|
elliptic "^6.6.1"
|
||||||
|
libsodium-wrappers-sumo "^0.7.11"
|
||||||
|
|
||||||
"@cosmjs/encoding@0.27.1":
|
"@cosmjs/encoding@0.27.1":
|
||||||
version "0.27.1"
|
version "0.27.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.27.1.tgz#3cd5bc0af743485eb2578cdb08cfa84c86d610e1"
|
resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.27.1.tgz#3cd5bc0af743485eb2578cdb08cfa84c86d610e1"
|
||||||
@@ -1561,6 +1584,14 @@
|
|||||||
"@cosmjs/stream" "^0.32.4"
|
"@cosmjs/stream" "^0.32.4"
|
||||||
xstream "^11.14.0"
|
xstream "^11.14.0"
|
||||||
|
|
||||||
|
"@cosmjs/json-rpc@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.33.1.tgz#a5b8459605750fa7d38c05aa6009a92010c0d042"
|
||||||
|
integrity sha512-T6VtWzecpmuTuMRGZWuBYHsMF/aznWCYUt/cGMWNSz7DBPipVd0w774PKpxXzpEbyt5sr61NiuLXc+Az15S/Cw==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/stream" "^0.33.1"
|
||||||
|
xstream "^11.14.0"
|
||||||
|
|
||||||
"@cosmjs/launchpad@^0.27.1":
|
"@cosmjs/launchpad@^0.27.1":
|
||||||
version "0.27.1"
|
version "0.27.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/launchpad/-/launchpad-0.27.1.tgz#b6f1995748be96560f5f01e84d3ff907477dda77"
|
resolved "https://registry.yarnpkg.com/@cosmjs/launchpad/-/launchpad-0.27.1.tgz#b6f1995748be96560f5f01e84d3ff907477dda77"
|
||||||
@@ -1595,7 +1626,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
bn.js "^5.2.0"
|
bn.js "^5.2.0"
|
||||||
|
|
||||||
"@cosmjs/proto-signing@^0.32.2", "@cosmjs/proto-signing@^0.32.3", "@cosmjs/proto-signing@^0.32.4":
|
"@cosmjs/math@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.33.1.tgz#04ae4cfdb05f04f1b13e908f9551ca85b13ba4d4"
|
||||||
|
integrity sha512-ytGkWdKFCPiiBU5eqjHNd59djPpIsOjbr2CkNjlnI1Zmdj+HDkSoD9MUGpz9/RJvRir5IvsXqdE05x8EtoQkJA==
|
||||||
|
dependencies:
|
||||||
|
bn.js "^5.2.0"
|
||||||
|
|
||||||
|
"@cosmjs/proto-signing@^0.32.2", "@cosmjs/proto-signing@^0.32.4":
|
||||||
version "0.32.4"
|
version "0.32.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.32.4.tgz#5a06e087c6d677439c8c9b25b5223d5e72c4cd93"
|
resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.32.4.tgz#5a06e087c6d677439c8c9b25b5223d5e72c4cd93"
|
||||||
integrity sha512-QdyQDbezvdRI4xxSlyM1rSVBO2st5sqtbEIl3IX03uJ7YiZIQHyv6vaHVf1V4mapusCqguiHJzm4N4gsFdLBbQ==
|
integrity sha512-QdyQDbezvdRI4xxSlyM1rSVBO2st5sqtbEIl3IX03uJ7YiZIQHyv6vaHVf1V4mapusCqguiHJzm4N4gsFdLBbQ==
|
||||||
@@ -1607,6 +1645,18 @@
|
|||||||
"@cosmjs/utils" "^0.32.4"
|
"@cosmjs/utils" "^0.32.4"
|
||||||
cosmjs-types "^0.9.0"
|
cosmjs-types "^0.9.0"
|
||||||
|
|
||||||
|
"@cosmjs/proto-signing@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.33.1.tgz#b084eb86410486cff30da7de34a636421db90ca8"
|
||||||
|
integrity sha512-Sv4W+MxX+0LVnd+2rU4Fw1HRsmMwSVSYULj7pRkij3wnPwUlTVoJjmKFgKz13ooIlfzPrz/dnNjGp/xnmXChFQ==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/amino" "^0.33.1"
|
||||||
|
"@cosmjs/crypto" "^0.33.1"
|
||||||
|
"@cosmjs/encoding" "^0.33.1"
|
||||||
|
"@cosmjs/math" "^0.33.1"
|
||||||
|
"@cosmjs/utils" "^0.33.1"
|
||||||
|
cosmjs-types "^0.9.0"
|
||||||
|
|
||||||
"@cosmjs/socket@^0.32.4":
|
"@cosmjs/socket@^0.32.4":
|
||||||
version "0.32.4"
|
version "0.32.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.32.4.tgz#86ab6adf3a442314774c0810b7a7cfcddf4f2082"
|
resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.32.4.tgz#86ab6adf3a442314774c0810b7a7cfcddf4f2082"
|
||||||
@@ -1617,7 +1667,17 @@
|
|||||||
ws "^7"
|
ws "^7"
|
||||||
xstream "^11.14.0"
|
xstream "^11.14.0"
|
||||||
|
|
||||||
"@cosmjs/stargate@^0.32.2", "@cosmjs/stargate@^0.32.3":
|
"@cosmjs/socket@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.33.1.tgz#2402487e7c70c8a5c801bd3189a58a09da786513"
|
||||||
|
integrity sha512-KzAeorten6Vn20sMiM6NNWfgc7jbyVo4Zmxev1FXa5EaoLCZy48cmT3hJxUJQvJP/lAy8wPGEjZ/u4rmF11x9A==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/stream" "^0.33.1"
|
||||||
|
isomorphic-ws "^4.0.1"
|
||||||
|
ws "^7"
|
||||||
|
xstream "^11.14.0"
|
||||||
|
|
||||||
|
"@cosmjs/stargate@^0.32.2":
|
||||||
version "0.32.4"
|
version "0.32.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.32.4.tgz#bd0e4d3bf613b629addbf5f875d3d3b50f640af1"
|
resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.32.4.tgz#bd0e4d3bf613b629addbf5f875d3d3b50f640af1"
|
||||||
integrity sha512-usj08LxBSsPRq9sbpCeVdyLx2guEcOHfJS9mHGCLCXpdAPEIEQEtWLDpEUc0LEhWOx6+k/ChXTc5NpFkdrtGUQ==
|
integrity sha512-usj08LxBSsPRq9sbpCeVdyLx2guEcOHfJS9mHGCLCXpdAPEIEQEtWLDpEUc0LEhWOx6+k/ChXTc5NpFkdrtGUQ==
|
||||||
@@ -1633,6 +1693,20 @@
|
|||||||
cosmjs-types "^0.9.0"
|
cosmjs-types "^0.9.0"
|
||||||
xstream "^11.14.0"
|
xstream "^11.14.0"
|
||||||
|
|
||||||
|
"@cosmjs/stargate@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.33.1.tgz#13972f710942ac728474051be4f9754814ccfb52"
|
||||||
|
integrity sha512-CnJ1zpSiaZgkvhk+9aTp5IPmgWn2uo+cNEBN8VuD9sD6BA0V4DMjqe251cNFLiMhkGtiE5I/WXFERbLPww3k8g==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/amino" "^0.33.1"
|
||||||
|
"@cosmjs/encoding" "^0.33.1"
|
||||||
|
"@cosmjs/math" "^0.33.1"
|
||||||
|
"@cosmjs/proto-signing" "^0.33.1"
|
||||||
|
"@cosmjs/stream" "^0.33.1"
|
||||||
|
"@cosmjs/tendermint-rpc" "^0.33.1"
|
||||||
|
"@cosmjs/utils" "^0.33.1"
|
||||||
|
cosmjs-types "^0.9.0"
|
||||||
|
|
||||||
"@cosmjs/stream@^0.32.4":
|
"@cosmjs/stream@^0.32.4":
|
||||||
version "0.32.4"
|
version "0.32.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.32.4.tgz#83e1f2285807467c56d9ea0e1113f79d9fa63802"
|
resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.32.4.tgz#83e1f2285807467c56d9ea0e1113f79d9fa63802"
|
||||||
@@ -1640,6 +1714,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
xstream "^11.14.0"
|
xstream "^11.14.0"
|
||||||
|
|
||||||
|
"@cosmjs/stream@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.33.1.tgz#2e928eb68c52253e64ab56a3047cd8039b66abde"
|
||||||
|
integrity sha512-bMUvEENjeQPSTx+YRzVsWT1uFIdHRcf4brsc14SOoRQ/j5rOJM/aHfsf/BmdSAnYbdOQ3CMKj/8nGAQ7xUdn7w==
|
||||||
|
dependencies:
|
||||||
|
xstream "^11.14.0"
|
||||||
|
|
||||||
"@cosmjs/tendermint-rpc@^0.32.2", "@cosmjs/tendermint-rpc@^0.32.4":
|
"@cosmjs/tendermint-rpc@^0.32.2", "@cosmjs/tendermint-rpc@^0.32.4":
|
||||||
version "0.32.4"
|
version "0.32.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.32.4.tgz#b36f9ec657498e42c97e21bb7368798ef6279752"
|
resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.32.4.tgz#b36f9ec657498e42c97e21bb7368798ef6279752"
|
||||||
@@ -1656,6 +1737,22 @@
|
|||||||
readonly-date "^1.0.0"
|
readonly-date "^1.0.0"
|
||||||
xstream "^11.14.0"
|
xstream "^11.14.0"
|
||||||
|
|
||||||
|
"@cosmjs/tendermint-rpc@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.33.1.tgz#5ab5b0b63e585badaa5827aef7e9e3d18695630a"
|
||||||
|
integrity sha512-22klDFq2MWnf//C8+rZ5/dYatr6jeGT+BmVbutXYfAK9fmODbtFcumyvB6uWaEORWfNukl8YK1OLuaWezoQvxA==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/crypto" "^0.33.1"
|
||||||
|
"@cosmjs/encoding" "^0.33.1"
|
||||||
|
"@cosmjs/json-rpc" "^0.33.1"
|
||||||
|
"@cosmjs/math" "^0.33.1"
|
||||||
|
"@cosmjs/socket" "^0.33.1"
|
||||||
|
"@cosmjs/stream" "^0.33.1"
|
||||||
|
"@cosmjs/utils" "^0.33.1"
|
||||||
|
axios "^1.6.0"
|
||||||
|
readonly-date "^1.0.0"
|
||||||
|
xstream "^11.14.0"
|
||||||
|
|
||||||
"@cosmjs/utils@0.27.1":
|
"@cosmjs/utils@0.27.1":
|
||||||
version "0.27.1"
|
version "0.27.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.27.1.tgz#1c8efde17256346ef142a3bd15158ee4055470e2"
|
resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.27.1.tgz#1c8efde17256346ef142a3bd15158ee4055470e2"
|
||||||
@@ -1671,6 +1768,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.32.4.tgz#a9a717c9fd7b1984d9cefdd0ef6c6f254060c671"
|
resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.32.4.tgz#a9a717c9fd7b1984d9cefdd0ef6c6f254060c671"
|
||||||
integrity sha512-D1Yc+Zy8oL/hkUkFUL/bwxvuDBzRGpc4cF7/SkdhxX4iHpSLgdOuTt1mhCh9+kl6NQREy9t7SYZ6xeW5gFe60w==
|
integrity sha512-D1Yc+Zy8oL/hkUkFUL/bwxvuDBzRGpc4cF7/SkdhxX4iHpSLgdOuTt1mhCh9+kl6NQREy9t7SYZ6xeW5gFe60w==
|
||||||
|
|
||||||
|
"@cosmjs/utils@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.33.1.tgz#8882cd26172cb5b0b692c179407d6c3904493fed"
|
||||||
|
integrity sha512-UnLHDY6KMmC+UXf3Ufyh+onE19xzEXjT4VZ504Acmk4PXxqyvG4cCPprlKUFnGUX7f0z8Or9MAOHXBx41uHBcg==
|
||||||
|
|
||||||
"@csstools/normalize.css@*":
|
"@csstools/normalize.css@*":
|
||||||
version "12.1.1"
|
version "12.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.1.1.tgz#f0ad221b7280f3fc814689786fd9ee092776ef8f"
|
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.1.1.tgz#f0ad221b7280f3fc814689786fd9ee092776ef8f"
|
||||||
@@ -2758,6 +2860,14 @@
|
|||||||
"@json-rpc-tools/types" "^1.7.6"
|
"@json-rpc-tools/types" "^1.7.6"
|
||||||
"@pedrouid/environment" "^1.0.1"
|
"@pedrouid/environment" "^1.0.1"
|
||||||
|
|
||||||
|
"@laconic-network/cosmjs-util@^0.1.0":
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://git.vdb.to/api/packages/LaconicNetwork/npm/%40laconic-network%2Fcosmjs-util/-/0.1.0/cosmjs-util-0.1.0.tgz#899af1de6101834e42c68aa684a00f51f7b913ed"
|
||||||
|
integrity sha512-18Gez/2FwDkLf/de1x6httgtwrj+9jldc7V6Or0KGGtboQgKPUC6G5RebowpeWR/d9GVORV3ha8H2PpCtH4XMQ==
|
||||||
|
dependencies:
|
||||||
|
"@cosmjs/math" "^0.33.1"
|
||||||
|
"@cosmjs/stargate" "^0.33.1"
|
||||||
|
|
||||||
"@leichtgewicht/ip-codec@^2.0.1":
|
"@leichtgewicht/ip-codec@^2.0.1":
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1"
|
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1"
|
||||||
@@ -7145,6 +7255,19 @@ elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.
|
|||||||
minimalistic-assert "^1.0.1"
|
minimalistic-assert "^1.0.1"
|
||||||
minimalistic-crypto-utils "^1.0.1"
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
|
elliptic@^6.6.1:
|
||||||
|
version "6.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06"
|
||||||
|
integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==
|
||||||
|
dependencies:
|
||||||
|
bn.js "^4.11.9"
|
||||||
|
brorand "^1.1.0"
|
||||||
|
hash.js "^1.0.0"
|
||||||
|
hmac-drbg "^1.0.1"
|
||||||
|
inherits "^2.0.4"
|
||||||
|
minimalistic-assert "^1.0.1"
|
||||||
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
emittery@^0.10.2:
|
emittery@^0.10.2:
|
||||||
version "0.10.2"
|
version "0.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933"
|
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933"
|
||||||
@@ -13618,16 +13741,7 @@ string-natural-compare@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -13730,7 +13844,7 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@@ -13744,13 +13858,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^4.1.0"
|
ansi-regex "^4.1.0"
|
||||||
|
|
||||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^7.0.1:
|
strip-ansi@^7.0.1:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||||
@@ -15066,7 +15173,7 @@ workbox-window@6.6.1:
|
|||||||
"@types/trusted-types" "^2.0.2"
|
"@types/trusted-types" "^2.0.2"
|
||||||
workbox-core "6.6.1"
|
workbox-core "6.6.1"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@@ -15084,15 +15191,6 @@ wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
|||||||
Reference in New Issue
Block a user