Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
# Test files (*.test.ts) are excluded — their docblocks legitimately name the
# forbidden tokens to document the policy; they cannot themselves move funds.
if grep -rE --include='*.ts' --include='*.vue' --exclude='*.test.ts' --exclude='*.spec.ts' \
'\b(signTx|signDirect|signAndBroadcast|broadcastTx|simulate[A-Za-z]*Tx|NolusWalletFactory)\b|@/networks/(cosm|evm|sol)/.*Wallet|wallet\.(sign|broadcast)' \
'\b(signTx|signDirect|signAndBroadcast|broadcastTx|simulate[A-Za-z]*Tx|NolusWalletFactory)\b|@/networks/(cosm|sol)/.*Wallet|wallet\.(sign|broadcast)' \
src/common/webmcp/ 2>/dev/null; then
echo "FAIL: src/common/webmcp/ references a signing/broadcast surface — policy violation."
echo " WebMCP scope is connect + read + navigate only. See src/common/webmcp/tools.ts docblock."
Expand Down
109 changes: 1 addition & 108 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"cosmjs-types-legacy": "npm:cosmjs-types@0.8.0",
"d3": "^7.9.0",
"dompurify": "^3.3.1",
"ethers": "^6.16.0",
"marked": "^17.0.0",
"motion-plus-vue": "^1.6.0",
"pinia": "^3.0.4",
Expand Down
2 changes: 1 addition & 1 deletion src/common/components/WalletInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const connections: {
icon: KeplrIcon,
label: i18n.t("message.keplr")
},
[WalletConnectMechanism.EVM_PHANTOM]: {
[WalletConnectMechanism.SOL_PHANTOM]: {
icon: PhantomIcon,
label: i18n.t("message.phantom")
},
Expand Down
12 changes: 6 additions & 6 deletions src/common/stores/wallet/actions/connectPhantom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import { NolusWalletFactory } from "@nolus/nolusjs";
import { WalletConnectMechanism } from "@/common/types";
import { Buffer } from "buffer";
import { IntercomService } from "@/common/utils/IntercomService";
import { MetaMaskWallet } from "@/networks/evm";
import { SolanaWallet } from "@/networks/sol";
import { applyNolusWalletOverrides } from "@/networks/cosm/NolusWalletOverride";

export async function connectPhantom(this: Store) {
const metamask = new MetaMaskWallet();
const { pubkeyAny } = await metamask.connect(WalletConnectMechanism.EVM_PHANTOM);
const signer = metamask.makeWCOfflineSigner();
const sol = new SolanaWallet("phantom");
const { pubkeyAny } = await sol.connect();
const signer = sol.makeWCOfflineSigner();

const nolusWalletOfflineSigner = await NolusWalletFactory.nolusOfflineSigner(signer);
await nolusWalletOfflineSigner.useAccount();

WalletManager.saveWalletConnectMechanism(WalletConnectMechanism.EVM_PHANTOM);
WalletManager.saveWalletConnectMechanism(WalletConnectMechanism.SOL_PHANTOM);
WalletManager.setPubKey(Buffer.from(pubkeyAny).toString("hex"));
applyWalletProtocolFilter(WalletConnectMechanism.EVM_PHANTOM);
applyWalletProtocolFilter(WalletConnectMechanism.SOL_PHANTOM);

this.wallet = nolusWalletOfflineSigner;
applyNolusWalletOverrides(this.wallet);
Expand Down
2 changes: 1 addition & 1 deletion src/common/stores/wallet/actions/connectSolflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SolanaWallet } from "@/networks/sol";
import { applyNolusWalletOverrides } from "@/networks/cosm/NolusWalletOverride";

export async function connectSolflare(this: Store) {
const sol = new SolanaWallet();
const sol = new SolanaWallet("solflare");
const { pubkeyAny } = await sol.connect();
const signer = sol.makeWCOfflineSigner();

Expand Down
2 changes: 1 addition & 1 deletion src/common/stores/wallet/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const actions = {
[WalletActions.DISCONNECT]: disconnect,
[WalletActions.CONNECT_KEPLR]: connectKeplr,
[WalletActions.CONNECT_LEDGER]: connectLedger,
[WalletActions.CONNECT_EVM_PHANTOM]: connectPhantom,
[WalletActions.CONNECT_SOL_PHANTOM]: connectPhantom,
[WalletActions.CONNECT_SOL_SOLFLARE]: connectSolflare,
[WalletActions.LOAD_VESTED_TOKENS]: loadVestedTokens,
[WalletActions.LOAD_APR]: loadApr,
Expand Down
41 changes: 28 additions & 13 deletions src/common/stores/wallet/actions/protocolFilter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,18 @@ vi.mock("@/common/utils", async () => {
};
});

// MetaMaskWallet (EVM) — stub `connect` and `makeWCOfflineSigner`.
vi.mock("@/networks/evm", () => ({
MetaMaskWallet: vi.fn().mockImplementation(() => ({
connect: vi.fn().mockResolvedValue({ pubkeyAny: new Uint8Array([1, 2, 3]) }),
makeWCOfflineSigner: vi.fn().mockReturnValue({})
}))
}));

// SolanaWallet — same shape as MetaMaskWallet.
// SolanaWallet — both Phantom and Solflare connect actions construct one.
// Capture the `provider` ctor arg per call so the assertions below can verify
// connectPhantom passes "phantom" and connectSolflare passes "solflare".
const solanaWalletCtor = vi.fn();
vi.mock("@/networks/sol", () => ({
SolanaWallet: vi.fn().mockImplementation(() => ({
connect: vi.fn().mockResolvedValue({ pubkeyAny: new Uint8Array([4, 5, 6]) }),
makeWCOfflineSigner: vi.fn().mockReturnValue({})
}))
SolanaWallet: vi.fn().mockImplementation((...args: unknown[]) => {
solanaWalletCtor(...args);
return {
connect: vi.fn().mockResolvedValue({ pubkeyAny: new Uint8Array([4, 5, 6]) }),
makeWCOfflineSigner: vi.fn().mockReturnValue({})
};
})
}));

// Ledger transports never run in jsdom. Stub create() to a minimal object.
Expand All @@ -139,6 +137,7 @@ import type { Store } from "../types";

beforeEach(() => {
setProtocolFilter.mockClear();
solanaWalletCtor.mockClear();
localStorage.clear();
});

Expand Down Expand Up @@ -188,6 +187,14 @@ describe("connectPhantom", () => {
expect(setProtocolFilter).toHaveBeenCalledTimes(1);
expect(setProtocolFilter).toHaveBeenCalledWith("SOLANA");
});

it("constructs SolanaWallet with provider='phantom'", async () => {
const store = makeStore();
await connectPhantom.call(store);

expect(solanaWalletCtor).toHaveBeenCalledTimes(1);
expect(solanaWalletCtor).toHaveBeenCalledWith("phantom");
});
});

describe("connectSolflare", () => {
Expand All @@ -198,6 +205,14 @@ describe("connectSolflare", () => {
expect(setProtocolFilter).toHaveBeenCalledTimes(1);
expect(setProtocolFilter).toHaveBeenCalledWith("SOLANA");
});

it("constructs SolanaWallet with provider='solflare'", async () => {
const store = makeStore();
await connectSolflare.call(store);

expect(solanaWalletCtor).toHaveBeenCalledTimes(1);
expect(solanaWalletCtor).toHaveBeenCalledWith("solflare");
});
});

describe("connectLedger (sunset, intentionally unwired)", () => {
Expand Down
36 changes: 34 additions & 2 deletions src/common/stores/wallet/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ vi.mock("./actions", () => ({
DISCONNECT: () => undefined,
CONNECT_KEPLR: () => undefined,
CONNECT_LEDGER: () => undefined,
CONNECT_EVM_PHANTOM: () => undefined,
CONNECT_SOL_PHANTOM: () => undefined,
CONNECT_SOL_SOLFLARE: () => undefined,
LOAD_VESTED_TOKENS: () => undefined,
LOAD_APR: () => undefined,
Expand All @@ -55,6 +55,8 @@ vi.mock("./getters", () => ({
}));

import { useWalletStore, WalletActions } from "./index";
import { WalletConnectMechanism } from "@/common/types";
import { walletActionMap } from "@/common/utils/WalletConnect";

describe("useWalletStore", () => {
beforeEach(() => {
Expand Down Expand Up @@ -110,7 +112,7 @@ describe("useWalletStore", () => {
expect(typeof store[WalletActions.DISCONNECT]).toBe("function");
expect(typeof store[WalletActions.CONNECT_KEPLR]).toBe("function");
expect(typeof store[WalletActions.CONNECT_LEDGER]).toBe("function");
expect(typeof store[WalletActions.CONNECT_EVM_PHANTOM]).toBe("function");
expect(typeof store[WalletActions.CONNECT_SOL_PHANTOM]).toBe("function");
expect(typeof store[WalletActions.CONNECT_SOL_SOLFLARE]).toBe("function");
expect(typeof store[WalletActions.LOAD_VESTED_TOKENS]).toBe("function");
expect(typeof store[WalletActions.LOAD_APR]).toBe("function");
Expand Down Expand Up @@ -140,6 +142,36 @@ describe("useWalletStore", () => {
// Smoke check that the enum re-export path is intact.
expect(WalletActions.DISCONNECT).toBe("DISCONNECT");
expect(WalletActions.CONNECT_KEPLR).toBe("CONNECT_KEPLR");
expect(WalletActions.CONNECT_SOL_PHANTOM).toBe("CONNECT_SOL_PHANTOM");
expect(WalletActions.LOAD_APR).toBe("LOAD_APR");
});

// End-to-end mechanism → action wiring guard.
//
// Catches the Pinia "action-name string-value desync" failure mode: if the
// WalletActions enum member name and its string value drift apart (e.g. the
// member is renamed but the string value is left as the old name), Pinia
// registers actions under the string value while the rest of the codebase
// looks them up by enum name — and the call vanishes silently.
//
// For every WalletConnectMechanism, walletActionMap must resolve to a
// WalletActions value, AND that value must be a callable function on the
// store surface.
describe("end-to-end mechanism → action wiring", () => {
it.each(Object.values(WalletConnectMechanism))(
"mechanism %s resolves to a registered store action",
(mechanism) => {
const action = walletActionMap[mechanism];
expect(action, `walletActionMap[${mechanism}] is undefined`).toBeDefined();
// The action key must be a WalletActions enum value.
expect(Object.values(WalletActions)).toContain(action);

const store = useWalletStore();
expect(
typeof store[action as keyof typeof store],
`store does not expose a function for action key ${action}`
).toBe("function");
}
);
});
});
2 changes: 1 addition & 1 deletion src/common/stores/wallet/types/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export enum WalletActions {
CONNECT_KEPLR = "CONNECT_KEPLR",
CONNECT_LEDGER = "CONNECT_LEDGER",
CONNECT_EVM_PHANTOM = "CONNECT_EVM_PHANTOM",
CONNECT_SOL_PHANTOM = "CONNECT_SOL_PHANTOM",
CONNECT_SOL_SOLFLARE = "CONNECT_SOL_SOLFLARE",

LOAD_VESTED_TOKENS = "LOAD_VESTED_TOKENS",
Expand Down
2 changes: 1 addition & 1 deletion src/common/types/WalletConnectMechanism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ export enum WalletConnectMechanism {
KEPLR = "extension",
LEDGER = "ledger",
LEDGER_BLUETOOTH = "ledger_bluetooth",
EVM_PHANTOM = "evm_phantom",
SOL_PHANTOM = "sol_phantom",
SOL_SOLFLARE = "sol_solflare"
}
8 changes: 4 additions & 4 deletions src/common/utils/WalletConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { WalletManager } from ".";
import { getCurrencyByDenom } from "./CurrencyLookup";
import { type NetworkData, WalletConnectMechanism } from "@/common/types";
import { authenticateKeplr, authenticateLedger, type BaseWallet, type Wallet } from "@/networks";
import { authenticateEvmPhantom, authenticateSolFlare } from "@/networks/cosm/WalletFactory";
import { authenticatePhantom, authenticateSolFlare } from "@/networks/cosm/WalletFactory";

export const validateAddress = (address: string) => {
if (!address || address.trim() == "") {
Expand Down Expand Up @@ -52,9 +52,9 @@ export const validateAmountV2 = (amount: string, amount2: string) => {
return "";
};

const walletActionMap: Record<WalletConnectMechanism, WalletActions> = {
export const walletActionMap: Record<WalletConnectMechanism, WalletActions> = {
[WalletConnectMechanism.KEPLR]: WalletActions.CONNECT_KEPLR,
[WalletConnectMechanism.EVM_PHANTOM]: WalletActions.CONNECT_EVM_PHANTOM,
[WalletConnectMechanism.SOL_PHANTOM]: WalletActions.CONNECT_SOL_PHANTOM,
[WalletConnectMechanism.SOL_SOLFLARE]: WalletActions.CONNECT_SOL_SOLFLARE,
[WalletConnectMechanism.LEDGER]: WalletActions.CONNECT_LEDGER,
[WalletConnectMechanism.LEDGER_BLUETOOTH]: WalletActions.CONNECT_LEDGER
Expand All @@ -65,7 +65,7 @@ const externalWalletMap: Record<
(wallet: Wallet, network: NetworkData) => Promise<BaseWallet | undefined>
> = {
[WalletConnectMechanism.KEPLR]: authenticateKeplr,
[WalletConnectMechanism.EVM_PHANTOM]: authenticateEvmPhantom,
[WalletConnectMechanism.SOL_PHANTOM]: authenticatePhantom,
[WalletConnectMechanism.SOL_SOLFLARE]: authenticateSolFlare,
[WalletConnectMechanism.LEDGER]: authenticateLedger,
[WalletConnectMechanism.LEDGER_BLUETOOTH]: authenticateLedger
Expand Down
Loading
Loading