@multiplatform.one/web3 7.88.2 → 7.88.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/connectkit/adapters/SolanaAdapter.cjs +81 -6
- package/dist/cjs/connectkit/adapters/SolanaAdapter.native.js +94 -6
- package/dist/cjs/connectkit/adapters/SolanaAdapter.native.js.map +1 -1
- package/dist/cjs/connectkit/adapters/discover.cjs +15 -0
- package/dist/cjs/connectkit/adapters/discover.native.js +17 -0
- package/dist/cjs/connectkit/adapters/discover.native.js.map +1 -1
- package/dist/cjs/connectkit/connectors/getWalletConnectors.cjs +4 -1
- package/dist/cjs/connectkit/connectors/getWalletConnectors.native.js +4 -1
- package/dist/cjs/connectkit/connectors/getWalletConnectors.native.js.map +1 -1
- package/dist/esm/connectkit/adapters/SolanaAdapter.mjs +79 -6
- package/dist/esm/connectkit/adapters/SolanaAdapter.mjs.map +1 -1
- package/dist/esm/connectkit/adapters/SolanaAdapter.native.js +92 -6
- package/dist/esm/connectkit/adapters/SolanaAdapter.native.js.map +1 -1
- package/dist/esm/connectkit/adapters/discover.mjs +16 -1
- package/dist/esm/connectkit/adapters/discover.mjs.map +1 -1
- package/dist/esm/connectkit/adapters/discover.native.js +18 -1
- package/dist/esm/connectkit/adapters/discover.native.js.map +1 -1
- package/dist/esm/connectkit/connectors/getWalletConnectors.mjs +4 -1
- package/dist/esm/connectkit/connectors/getWalletConnectors.mjs.map +1 -1
- package/dist/esm/connectkit/connectors/getWalletConnectors.native.js +4 -1
- package/dist/esm/connectkit/connectors/getWalletConnectors.native.js.map +1 -1
- package/dist/jsx/connectkit/adapters/SolanaAdapter.mjs +79 -6
- package/dist/jsx/connectkit/adapters/SolanaAdapter.mjs.map +1 -1
- package/dist/jsx/connectkit/adapters/SolanaAdapter.native.js +94 -6
- package/dist/jsx/connectkit/adapters/SolanaAdapter.native.js.map +1 -1
- package/dist/jsx/connectkit/adapters/discover.mjs +16 -1
- package/dist/jsx/connectkit/adapters/discover.mjs.map +1 -1
- package/dist/jsx/connectkit/adapters/discover.native.js +17 -0
- package/dist/jsx/connectkit/adapters/discover.native.js.map +1 -1
- package/dist/jsx/connectkit/connectors/getWalletConnectors.mjs +4 -1
- package/dist/jsx/connectkit/connectors/getWalletConnectors.mjs.map +1 -1
- package/dist/jsx/connectkit/connectors/getWalletConnectors.native.js +4 -1
- package/dist/jsx/connectkit/connectors/getWalletConnectors.native.js.map +1 -1
- package/dist/keycloak/login.iife.js +251 -251
- package/package.json +7 -7
- package/src/connectkit/adapters/SolanaAdapter.ts +102 -6
- package/src/connectkit/adapters/discover.ts +19 -2
- package/src/connectkit/connectors/getWalletConnectors.ts +1 -0
- package/src/keycloak/KeycloakWalletLogin.solana.spec.tsx +158 -0
- package/types/connectkit/adapters/SolanaAdapter.d.ts +37 -0
- package/types/connectkit/adapters/SolanaAdapter.d.ts.map +1 -1
- package/types/connectkit/adapters/discover.d.ts +5 -1
- package/types/connectkit/adapters/discover.d.ts.map +1 -1
- package/types/connectkit/connectors/getWalletConnectors.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/web3",
|
|
3
|
-
"version": "7.88.
|
|
3
|
+
"version": "7.88.4",
|
|
4
4
|
"description": "Web3 wallet integration for multiplatform.one ecosystem with ConnectKit and wagmi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"connectkit",
|
|
@@ -120,10 +120,10 @@
|
|
|
120
120
|
"@aave/account": "^0.2.1",
|
|
121
121
|
"@coinbase/wallet-sdk": "^4.4.0",
|
|
122
122
|
"@metamask/connect-evm": "^2.1.1",
|
|
123
|
-
"@multiplatform.one/connectkit": "^7.88.
|
|
124
|
-
"@multiplatform.one/gpg": "^7.88.
|
|
125
|
-
"@multiplatform.one/platform": "^7.88.
|
|
126
|
-
"@multiplatform.one/theme": "^7.88.
|
|
123
|
+
"@multiplatform.one/connectkit": "^7.88.4",
|
|
124
|
+
"@multiplatform.one/gpg": "^7.88.4",
|
|
125
|
+
"@multiplatform.one/platform": "^7.88.4",
|
|
126
|
+
"@multiplatform.one/theme": "^7.88.4",
|
|
127
127
|
"@noble/ciphers": "^1.3.0",
|
|
128
128
|
"@noble/curves": "^1.9.7",
|
|
129
129
|
"@phosphor-icons/react": "^2.1.10",
|
|
@@ -147,8 +147,8 @@
|
|
|
147
147
|
"wagmi": "^3.7.1"
|
|
148
148
|
},
|
|
149
149
|
"devDependencies": {
|
|
150
|
-
"@multiplatform.one/config": "7.88.
|
|
151
|
-
"@multiplatform.one/test-utils": "7.88.
|
|
150
|
+
"@multiplatform.one/config": "7.88.4",
|
|
151
|
+
"@multiplatform.one/test-utils": "7.88.4",
|
|
152
152
|
"@tamagui/build": "2.7.6",
|
|
153
153
|
"@tanstack/react-query": "^5.104.3",
|
|
154
154
|
"@testing-library/jest-dom": "^7.0.1",
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
// (A future system-browser / Mobile-Wallet-Adapter path — the Solana analogue
|
|
12
12
|
// of a relay — would be a separate adapter, same as the Ethereum gap.)
|
|
13
13
|
|
|
14
|
+
import {
|
|
15
|
+
SolanaSignMessage,
|
|
16
|
+
StandardConnect,
|
|
17
|
+
type SolanaSignMessageFeature,
|
|
18
|
+
type StandardConnectFeature,
|
|
19
|
+
type StandardWallet,
|
|
20
|
+
type StandardWalletAccount,
|
|
21
|
+
} from "../../clients/walletStandard";
|
|
14
22
|
import { buildWalletMessage } from "../../message";
|
|
15
23
|
import type { SignInMessageOptions, WalletAdapter, WalletAddress } from "./types";
|
|
16
24
|
|
|
@@ -39,7 +47,7 @@ function base58encode(bytes: Uint8Array): string {
|
|
|
39
47
|
|
|
40
48
|
type SolanaListener = (publicKey?: { toString(): string } | null) => void;
|
|
41
49
|
|
|
42
|
-
interface InjectedSolana {
|
|
50
|
+
export interface InjectedSolana {
|
|
43
51
|
isPhantom?: boolean;
|
|
44
52
|
publicKey?: { toString(): string } | null;
|
|
45
53
|
connect(opts?: { onlyIfTrusted?: boolean }): Promise<{ publicKey: { toString(): string } }>;
|
|
@@ -64,14 +72,102 @@ function getInjectedProvider(): InjectedSolana {
|
|
|
64
72
|
return provider;
|
|
65
73
|
}
|
|
66
74
|
|
|
75
|
+
interface StandardEventsFeature {
|
|
76
|
+
on(
|
|
77
|
+
event: "change",
|
|
78
|
+
listener: (properties: { accounts?: readonly StandardWalletAccount[] }) => void,
|
|
79
|
+
): () => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface StandardDisconnectFeature {
|
|
83
|
+
disconnect(): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const isSolanaAccount = (account: StandardWalletAccount) =>
|
|
87
|
+
account.chains.some((chain) => chain.startsWith("solana:"));
|
|
88
|
+
|
|
89
|
+
/** A Wallet Standard wallet that can connect and sign a message on a Solana chain. */
|
|
90
|
+
export function isStandardSolanaWallet(wallet: StandardWallet): boolean {
|
|
91
|
+
return (
|
|
92
|
+
!!wallet.features[StandardConnect] &&
|
|
93
|
+
!!wallet.features[SolanaSignMessage] &&
|
|
94
|
+
wallet.chains.some((chain) => chain.startsWith("solana:"))
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A Wallet Standard Solana wallet (Solflare, Backpack, Phantom) behind the
|
|
100
|
+
* injected provider's shape, so `SolanaAdapter` drives it the same way. A
|
|
101
|
+
* silent connect the wallet answers with no account is refused as 4001, as an
|
|
102
|
+
* untrusting injected wallet refuses `onlyIfTrusted`.
|
|
103
|
+
*/
|
|
104
|
+
export function standardSolanaProvider(wallet: StandardWallet): InjectedSolana {
|
|
105
|
+
const connectFeature = wallet.features[StandardConnect] as StandardConnectFeature;
|
|
106
|
+
const signFeature = wallet.features[SolanaSignMessage] as SolanaSignMessageFeature;
|
|
107
|
+
const events = wallet.features["standard:events"] as StandardEventsFeature | undefined;
|
|
108
|
+
const disconnectFeature = wallet.features["standard:disconnect"] as
|
|
109
|
+
| StandardDisconnectFeature
|
|
110
|
+
| undefined;
|
|
111
|
+
const key = (account: StandardWalletAccount) => ({ toString: () => account.address });
|
|
112
|
+
const pick = (accounts: readonly StandardWalletAccount[]) =>
|
|
113
|
+
accounts.find(isSolanaAccount) ?? null;
|
|
114
|
+
const unsubscribes = new Map<SolanaListener, () => void>();
|
|
115
|
+
let account: StandardWalletAccount | null = null;
|
|
116
|
+
return {
|
|
117
|
+
get publicKey() {
|
|
118
|
+
return account ? key(account) : null;
|
|
119
|
+
},
|
|
120
|
+
async connect(options) {
|
|
121
|
+
const { accounts } = await connectFeature.connect(
|
|
122
|
+
options?.onlyIfTrusted ? { silent: true } : undefined,
|
|
123
|
+
);
|
|
124
|
+
account = pick(accounts);
|
|
125
|
+
if (!account) {
|
|
126
|
+
throw Object.assign(new Error(`${wallet.name} shared no Solana account.`), { code: 4001 });
|
|
127
|
+
}
|
|
128
|
+
return { publicKey: key(account) };
|
|
129
|
+
},
|
|
130
|
+
async disconnect() {
|
|
131
|
+
account = null;
|
|
132
|
+
await disconnectFeature?.disconnect();
|
|
133
|
+
},
|
|
134
|
+
async signMessage(message) {
|
|
135
|
+
if (!account) throw new Error("connect a Solana wallet first");
|
|
136
|
+
const [signed] = await signFeature.signMessage({ account, message });
|
|
137
|
+
return { signature: signed.signature };
|
|
138
|
+
},
|
|
139
|
+
on(event, listener) {
|
|
140
|
+
if (!events) return;
|
|
141
|
+
const unsubscribe = events.on("change", ({ accounts }) => {
|
|
142
|
+
if (!accounts) return;
|
|
143
|
+
account = pick(accounts);
|
|
144
|
+
if (event === "accountChanged" && account) listener(key(account));
|
|
145
|
+
if (event === "disconnect" && !account) listener(null);
|
|
146
|
+
});
|
|
147
|
+
unsubscribes.set(listener, unsubscribe);
|
|
148
|
+
},
|
|
149
|
+
off(_event, listener) {
|
|
150
|
+
unsubscribes.get(listener)?.();
|
|
151
|
+
unsubscribes.delete(listener);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
67
156
|
export class SolanaAdapter implements WalletAdapter {
|
|
68
157
|
readonly chain = "solana";
|
|
69
158
|
address: WalletAddress | null = null;
|
|
70
159
|
networkId: string | null = "mainnet-beta";
|
|
71
160
|
isConnected = false;
|
|
72
161
|
|
|
162
|
+
/** Drives the injected wallet unless given another provider, such as `standardSolanaProvider(wallet)`. */
|
|
163
|
+
private readonly provider: () => InjectedSolana;
|
|
164
|
+
|
|
165
|
+
constructor(provider: () => InjectedSolana = getInjectedProvider) {
|
|
166
|
+
this.provider = provider;
|
|
167
|
+
}
|
|
168
|
+
|
|
73
169
|
async connect(): Promise<void> {
|
|
74
|
-
const provider =
|
|
170
|
+
const provider = this.provider();
|
|
75
171
|
const { publicKey } = await provider.connect();
|
|
76
172
|
this.address = publicKey.toString();
|
|
77
173
|
this.isConnected = true;
|
|
@@ -83,7 +179,7 @@ export class SolanaAdapter implements WalletAdapter {
|
|
|
83
179
|
* any other failure, and when no wallet is injected yet.
|
|
84
180
|
*/
|
|
85
181
|
async restore(): Promise<boolean> {
|
|
86
|
-
const provider =
|
|
182
|
+
const provider = this.provider();
|
|
87
183
|
try {
|
|
88
184
|
const { publicKey } = await provider.connect({ onlyIfTrusted: true });
|
|
89
185
|
this.address = publicKey.toString();
|
|
@@ -101,7 +197,7 @@ export class SolanaAdapter implements WalletAdapter {
|
|
|
101
197
|
* page must connect again; the adapter treats it as a disconnect.
|
|
102
198
|
*/
|
|
103
199
|
subscribe(listener: () => void): () => void {
|
|
104
|
-
const provider =
|
|
200
|
+
const provider = this.provider();
|
|
105
201
|
const onAccount: SolanaListener = (publicKey) => {
|
|
106
202
|
this.address = publicKey ? publicKey.toString() : null;
|
|
107
203
|
this.isConnected = this.address !== null;
|
|
@@ -123,7 +219,7 @@ export class SolanaAdapter implements WalletAdapter {
|
|
|
123
219
|
|
|
124
220
|
async disconnect(): Promise<void> {
|
|
125
221
|
try {
|
|
126
|
-
await
|
|
222
|
+
await this.provider().disconnect();
|
|
127
223
|
} catch {
|
|
128
224
|
// ignore — clearing local state below is what matters
|
|
129
225
|
}
|
|
@@ -142,7 +238,7 @@ export class SolanaAdapter implements WalletAdapter {
|
|
|
142
238
|
|
|
143
239
|
/** Sign the UTF-8 message bytes; returns the base58 ed25519 signature. */
|
|
144
240
|
async signMessage(message: string): Promise<string> {
|
|
145
|
-
const provider =
|
|
241
|
+
const provider = this.provider();
|
|
146
242
|
const { signature } = await provider.signMessage(new TextEncoder().encode(message), "utf8");
|
|
147
243
|
return base58encode(signature);
|
|
148
244
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { getStandardWallets } from "../../clients/walletStandard";
|
|
1
2
|
import type { WalletAdapter } from "./types";
|
|
2
|
-
import { SolanaAdapter } from "./SolanaAdapter";
|
|
3
|
+
import { SolanaAdapter, isStandardSolanaWallet, standardSolanaProvider } from "./SolanaAdapter";
|
|
3
4
|
import { SuiAdapter, discoverSuiWallets } from "./SuiAdapter";
|
|
4
5
|
import { BitcoinAdapter } from "./BitcoinAdapter";
|
|
5
6
|
|
|
@@ -14,10 +15,14 @@ export interface WalletEntry {
|
|
|
14
15
|
ecosystem: WalletEcosystem;
|
|
15
16
|
/** Ready-to-drive wallet adapter. */
|
|
16
17
|
adapter: WalletAdapter;
|
|
18
|
+
/** The wallet's own icon (a data URI), for a wallet no walletConfigs key names. */
|
|
19
|
+
icon?: string;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
|
-
* Detect the non-EVM wallets present in the page (Solana / Sui / Bitcoin).
|
|
23
|
+
* Detect the non-EVM wallets present in the page (Solana / Sui / Bitcoin). A
|
|
24
|
+
* Solana wallet is the injected one, else the first Wallet Standard wallet that
|
|
25
|
+
* signs on a Solana chain. EVM
|
|
21
26
|
* wallets are intentionally NOT returned here — they are handled natively by
|
|
22
27
|
* wagmi's EIP-6963 / Coinbase / MetaMask connectors. One entry per
|
|
23
28
|
* (ecosystem, primary wallet); each carries a ready wallet adapter.
|
|
@@ -40,6 +45,18 @@ export function discoverWallets(): WalletEntry[] {
|
|
|
40
45
|
ecosystem: "solana",
|
|
41
46
|
adapter: new SolanaAdapter(),
|
|
42
47
|
});
|
|
48
|
+
} else {
|
|
49
|
+
const standard = getStandardWallets().find(isStandardSolanaWallet);
|
|
50
|
+
if (standard) {
|
|
51
|
+
const provider = standardSolanaProvider(standard);
|
|
52
|
+
out.push({
|
|
53
|
+
id: `wallet:solana:${standard.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`,
|
|
54
|
+
name: standard.name,
|
|
55
|
+
ecosystem: "solana",
|
|
56
|
+
adapter: new SolanaAdapter(() => provider),
|
|
57
|
+
...(standard.icon ? { icon: standard.icon } : {}),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
const suiWallets = discoverSuiWallets();
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { ed25519 } from "@noble/curves/ed25519";
|
|
3
|
+
import bs58 from "bs58";
|
|
4
|
+
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type { StandardWallet, StandardWalletAccount } from "../clients/walletStandard";
|
|
7
|
+
import { discoverWallets } from "../connectkit/adapters/discover";
|
|
8
|
+
import { verifySiwsSignature } from "../siws/verify";
|
|
9
|
+
import { KeycloakWalletLogin } from "./KeycloakWalletLogin";
|
|
10
|
+
|
|
11
|
+
const LOGIN_ACTION =
|
|
12
|
+
"https://kc.example.com/realms/main/login-actions/authenticate?session_code=sc&execution=ex";
|
|
13
|
+
|
|
14
|
+
function keypair() {
|
|
15
|
+
const secret = ed25519.utils.randomPrivateKey();
|
|
16
|
+
return { secret, address: bs58.encode(ed25519.getPublicKey(secret)) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A Wallet Standard wallet, registered the way Solflare and Backpack register. */
|
|
20
|
+
function registerStandardWallet(
|
|
21
|
+
name: string,
|
|
22
|
+
chains: readonly string[],
|
|
23
|
+
{ secret, address }: ReturnType<typeof keypair>,
|
|
24
|
+
) {
|
|
25
|
+
const account: StandardWalletAccount = {
|
|
26
|
+
address,
|
|
27
|
+
publicKey: bs58.decode(address),
|
|
28
|
+
chains,
|
|
29
|
+
features: ["solana:signMessage"],
|
|
30
|
+
};
|
|
31
|
+
const signMessage = vi.fn(async (...inputs: { message: Uint8Array }[]) =>
|
|
32
|
+
inputs.map(({ message }) => ({
|
|
33
|
+
signedMessage: message,
|
|
34
|
+
signature: ed25519.sign(message, secret),
|
|
35
|
+
})),
|
|
36
|
+
);
|
|
37
|
+
const wallet: StandardWallet = {
|
|
38
|
+
name,
|
|
39
|
+
icon: "data:image/svg+xml;base64,PHN2Zy8+",
|
|
40
|
+
chains,
|
|
41
|
+
accounts: [],
|
|
42
|
+
features: {
|
|
43
|
+
"standard:connect": { connect: async () => ({ accounts: [account] }) },
|
|
44
|
+
"standard:events": { on: () => () => {} },
|
|
45
|
+
"solana:signMessage": { signMessage },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
const register = (event: Event) =>
|
|
49
|
+
((event as CustomEvent).detail as { register: (w: StandardWallet) => void }).register(wallet);
|
|
50
|
+
window.addEventListener("wallet-standard:app-ready", register);
|
|
51
|
+
return {
|
|
52
|
+
signMessage,
|
|
53
|
+
unregister: () => window.removeEventListener("wallet-standard:app-ready", register),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const nonceRequests: string[] = [];
|
|
58
|
+
const cleanups: (() => void)[] = [];
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
nonceRequests.length = 0;
|
|
62
|
+
vi.stubGlobal(
|
|
63
|
+
"fetch",
|
|
64
|
+
vi.fn(async (input: RequestInfo | URL) => {
|
|
65
|
+
const url = String(input);
|
|
66
|
+
if (!url.endsWith("/nonce")) return new Response("not found", { status: 404 });
|
|
67
|
+
nonceRequests.push(url);
|
|
68
|
+
return new Response("nonce123abc");
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
cleanup();
|
|
75
|
+
for (const undo of cleanups.splice(0)) undo();
|
|
76
|
+
vi.unstubAllGlobals();
|
|
77
|
+
vi.restoreAllMocks();
|
|
78
|
+
Reflect.deleteProperty(window, "phantom");
|
|
79
|
+
document.querySelectorAll("form").forEach((form) => form.remove());
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
async function signInThrough(walletName: string) {
|
|
83
|
+
const submit = vi
|
|
84
|
+
.spyOn(HTMLFormElement.prototype, "submit")
|
|
85
|
+
.mockImplementation(function (this: HTMLFormElement) {});
|
|
86
|
+
render(
|
|
87
|
+
<KeycloakWalletLogin
|
|
88
|
+
baseUrl="https://kc.example.com"
|
|
89
|
+
realm="main"
|
|
90
|
+
resource="wallet"
|
|
91
|
+
target={{ action: LOGIN_ACTION }}
|
|
92
|
+
domain="kc.example.com"
|
|
93
|
+
/>,
|
|
94
|
+
);
|
|
95
|
+
fireEvent.click(document.getElementById("mpo-wallet-login") as HTMLButtonElement);
|
|
96
|
+
fireEvent.click(await screen.findByText(walletName));
|
|
97
|
+
await waitFor(() => expect(submit).toHaveBeenCalledTimes(1));
|
|
98
|
+
const form = submit.mock.contexts[0] as HTMLFormElement;
|
|
99
|
+
const field = (name: string) =>
|
|
100
|
+
(form.querySelector(`input[name="${name}"]`) as HTMLInputElement).value;
|
|
101
|
+
return { form, signature: field("username"), message: atob(field("password")) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe("KeycloakWalletLogin Solana sign-in (FZ-1824)", () => {
|
|
105
|
+
it("lists a Wallet Standard Solana wallet and posts its signed SIWS message to the login action", async () => {
|
|
106
|
+
const keys = keypair();
|
|
107
|
+
const wallet = registerStandardWallet("Solflare", ["solana:mainnet"], keys);
|
|
108
|
+
cleanups.push(wallet.unregister);
|
|
109
|
+
|
|
110
|
+
const { form, signature, message } = await signInThrough("Solflare");
|
|
111
|
+
|
|
112
|
+
expect(form.action).toBe(LOGIN_ACTION);
|
|
113
|
+
expect(message.split("\n").slice(0, 2)).toEqual([
|
|
114
|
+
"kc.example.com wants you to sign in with your Solana account:",
|
|
115
|
+
keys.address,
|
|
116
|
+
]);
|
|
117
|
+
expect(message).toContain("Chain ID: solana:mainnet");
|
|
118
|
+
expect(message).toContain("Nonce: nonce123abc");
|
|
119
|
+
expect(verifySiwsSignature(message, signature, keys.address)).toBe(true);
|
|
120
|
+
expect(wallet.signMessage).toHaveBeenCalledTimes(1);
|
|
121
|
+
expect(nonceRequests).toEqual(["https://kc.example.com/realms/main/wallet/nonce"]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("signs with the injected Phantom too, one nonce for the page", async () => {
|
|
125
|
+
const keys = keypair();
|
|
126
|
+
(window as unknown as Record<string, unknown>).phantom = {
|
|
127
|
+
solana: {
|
|
128
|
+
isPhantom: true,
|
|
129
|
+
publicKey: null,
|
|
130
|
+
connect: async () => ({ publicKey: { toString: () => keys.address } }),
|
|
131
|
+
disconnect: async () => {},
|
|
132
|
+
signMessage: async (bytes: Uint8Array) => ({ signature: ed25519.sign(bytes, keys.secret) }),
|
|
133
|
+
on() {},
|
|
134
|
+
off() {},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const { signature, message } = await signInThrough("Phantom");
|
|
139
|
+
|
|
140
|
+
expect(message.split("\n")[1]).toBe(keys.address);
|
|
141
|
+
expect(verifySiwsSignature(message, signature, keys.address)).toBe(true);
|
|
142
|
+
expect(nonceRequests).toEqual(["https://kc.example.com/realms/main/wallet/nonce"]);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("Solana wallet discovery", () => {
|
|
147
|
+
it("prefers the injected wallet, so a Phantom that also registers is listed once", () => {
|
|
148
|
+
(window as unknown as Record<string, unknown>).phantom = { solana: { isPhantom: true } };
|
|
149
|
+
cleanups.push(registerStandardWallet("Phantom", ["solana:mainnet"], keypair()).unregister);
|
|
150
|
+
const solana = discoverWallets().filter((entry) => entry.ecosystem === "solana");
|
|
151
|
+
expect(solana.map((entry) => entry.id)).toEqual(["wallet:solana:phantom"]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("skips a Wallet Standard wallet that signs on no Solana chain", () => {
|
|
155
|
+
cleanups.push(registerStandardWallet("Sui Only", ["sui:mainnet"], keypair()).unregister);
|
|
156
|
+
expect(discoverWallets().filter((entry) => entry.ecosystem === "solana")).toEqual([]);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -1,9 +1,45 @@
|
|
|
1
|
+
import { type StandardWallet } from "../../clients/walletStandard";
|
|
1
2
|
import type { SignInMessageOptions, WalletAdapter, WalletAddress } from "./types";
|
|
3
|
+
type SolanaListener = (publicKey?: {
|
|
4
|
+
toString(): string;
|
|
5
|
+
} | null) => void;
|
|
6
|
+
export interface InjectedSolana {
|
|
7
|
+
isPhantom?: boolean;
|
|
8
|
+
publicKey?: {
|
|
9
|
+
toString(): string;
|
|
10
|
+
} | null;
|
|
11
|
+
connect(opts?: {
|
|
12
|
+
onlyIfTrusted?: boolean;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
publicKey: {
|
|
15
|
+
toString(): string;
|
|
16
|
+
};
|
|
17
|
+
}>;
|
|
18
|
+
disconnect(): Promise<void>;
|
|
19
|
+
signMessage(message: Uint8Array, display?: "utf8" | "hex"): Promise<{
|
|
20
|
+
signature: Uint8Array;
|
|
21
|
+
}>;
|
|
22
|
+
on?(event: "accountChanged" | "disconnect", listener: SolanaListener): void;
|
|
23
|
+
off?(event: "accountChanged" | "disconnect", listener: SolanaListener): void;
|
|
24
|
+
removeListener?(event: "accountChanged" | "disconnect", listener: SolanaListener): void;
|
|
25
|
+
}
|
|
26
|
+
/** A Wallet Standard wallet that can connect and sign a message on a Solana chain. */
|
|
27
|
+
export declare function isStandardSolanaWallet(wallet: StandardWallet): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* A Wallet Standard Solana wallet (Solflare, Backpack, Phantom) behind the
|
|
30
|
+
* injected provider's shape, so `SolanaAdapter` drives it the same way. A
|
|
31
|
+
* silent connect the wallet answers with no account is refused as 4001, as an
|
|
32
|
+
* untrusting injected wallet refuses `onlyIfTrusted`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function standardSolanaProvider(wallet: StandardWallet): InjectedSolana;
|
|
2
35
|
export declare class SolanaAdapter implements WalletAdapter {
|
|
3
36
|
readonly chain = "solana";
|
|
4
37
|
address: WalletAddress | null;
|
|
5
38
|
networkId: string | null;
|
|
6
39
|
isConnected: boolean;
|
|
40
|
+
/** Drives the injected wallet unless given another provider, such as `standardSolanaProvider(wallet)`. */
|
|
41
|
+
private readonly provider;
|
|
42
|
+
constructor(provider?: () => InjectedSolana);
|
|
7
43
|
connect(): Promise<void>;
|
|
8
44
|
/**
|
|
9
45
|
* Reconnects without a prompt (`onlyIfTrusted`). Resolves false when the
|
|
@@ -23,4 +59,5 @@ export declare class SolanaAdapter implements WalletAdapter {
|
|
|
23
59
|
/** Sign the UTF-8 message bytes; returns the base58 ed25519 signature. */
|
|
24
60
|
signMessage(message: string): Promise<string>;
|
|
25
61
|
}
|
|
62
|
+
export {};
|
|
26
63
|
//# sourceMappingURL=SolanaAdapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SolanaAdapter.d.ts","sourceRoot":"","sources":["../../../src/connectkit/adapters/SolanaAdapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SolanaAdapter.d.ts","sourceRoot":"","sources":["../../../src/connectkit/adapters/SolanaAdapter.ts"],"names":[],"mappings":"AAaA,OAAO,EAKL,KAAK,cAAc,EAEpB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAyBlF,KAAK,cAAc,GAAG,CAAC,SAAS,CAAC,EAAE;IAAE,QAAQ,IAAI,MAAM,CAAA;CAAE,GAAG,IAAI,KAAK,IAAI,CAAC;AAE1E,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE;QAAE,QAAQ,IAAI,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1C,OAAO,CAAC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;IAC5F,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IAC/F,EAAE,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5E,GAAG,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7E,cAAc,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;CACzF;AA8BD,sFAAsF;AACtF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAMtE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAkD7E;AAED,qBAAa,aAAc,YAAW,aAAa;IACjD,QAAQ,CAAC,KAAK,YAAY;IAC1B,OAAO,EAAE,aAAa,GAAG,IAAI,CAAQ;IACrC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAkB;IAC1C,WAAW,UAAS;IAEpB,0GAA0G;IAC1G,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;gBAEpC,QAAQ,GAAE,MAAM,cAAoC;IAI1D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAajC;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAqBrC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC,4EAA4E;IACtE,YAAY,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ/D,0EAA0E;IACpE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAKpD"}
|
|
@@ -9,9 +9,13 @@ export interface WalletEntry {
|
|
|
9
9
|
ecosystem: WalletEcosystem;
|
|
10
10
|
/** Ready-to-drive wallet adapter. */
|
|
11
11
|
adapter: WalletAdapter;
|
|
12
|
+
/** The wallet's own icon (a data URI), for a wallet no walletConfigs key names. */
|
|
13
|
+
icon?: string;
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
|
-
* Detect the non-EVM wallets present in the page (Solana / Sui / Bitcoin).
|
|
16
|
+
* Detect the non-EVM wallets present in the page (Solana / Sui / Bitcoin). A
|
|
17
|
+
* Solana wallet is the injected one, else the first Wallet Standard wallet that
|
|
18
|
+
* signs on a Solana chain. EVM
|
|
15
19
|
* wallets are intentionally NOT returned here — they are handled natively by
|
|
16
20
|
* wagmi's EIP-6963 / Coinbase / MetaMask connectors. One entry per
|
|
17
21
|
* (ecosystem, primary wallet); each carries a ready wallet adapter.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/connectkit/adapters/discover.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/connectkit/adapters/discover.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,SAAS,EAAE,eAAe,CAAC;IAC3B,qCAAqC;IACrC,OAAO,EAAE,aAAa,CAAC;IACvB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,WAAW,EAAE,CAqD/C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getWalletConnectors.d.ts","sourceRoot":"","sources":["../../../src/connectkit/connectors/getWalletConnectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGzE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,WAAW,EAAsB,GACzC,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"getWalletConnectors.d.ts","sourceRoot":"","sources":["../../../src/connectkit/connectors/getWalletConnectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGzE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,WAAW,EAAsB,GACzC,iBAAiB,EAAE,CAUrB"}
|