@multiplatform.one/web3 7.32.0 → 7.33.1
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/README.md +21 -0
- package/dist/cjs/connectkit/connectors/metaMaskConnectConnector.cjs +14 -3
- package/dist/cjs/connectkit/connectors/metaMaskConnectConnector.native.js +14 -3
- package/dist/cjs/connectkit/connectors/metaMaskConnectConnector.native.js.map +1 -1
- package/dist/cjs/phantom/evm.cjs +133 -0
- package/dist/cjs/phantom/evm.native.js +269 -0
- package/dist/cjs/phantom/evm.native.js.map +1 -0
- package/dist/cjs/phantom/index.cjs +36 -0
- package/dist/cjs/phantom/index.native.js +39 -0
- package/dist/cjs/phantom/index.native.js.map +1 -0
- package/dist/cjs/phantom/urls.cjs +33 -0
- package/dist/cjs/phantom/urls.native.js +38 -0
- package/dist/cjs/phantom/urls.native.js.map +1 -0
- package/dist/esm/connectkit/connectors/metaMaskConnectConnector.mjs +14 -3
- package/dist/esm/connectkit/connectors/metaMaskConnectConnector.mjs.map +1 -1
- package/dist/esm/connectkit/connectors/metaMaskConnectConnector.native.js +14 -3
- package/dist/esm/connectkit/connectors/metaMaskConnectConnector.native.js.map +1 -1
- package/dist/esm/phantom/evm.mjs +103 -0
- package/dist/esm/phantom/evm.mjs.map +1 -0
- package/dist/esm/phantom/evm.native.js +236 -0
- package/dist/esm/phantom/evm.native.js.map +1 -0
- package/dist/esm/phantom/index.mjs +4 -0
- package/dist/esm/phantom/index.mjs.map +1 -0
- package/dist/esm/phantom/index.native.js +4 -0
- package/dist/esm/phantom/index.native.js.map +1 -0
- package/dist/esm/phantom/urls.mjs +7 -0
- package/dist/esm/phantom/urls.mjs.map +1 -0
- package/dist/esm/phantom/urls.native.js +9 -0
- package/dist/esm/phantom/urls.native.js.map +1 -0
- package/dist/jsx/connectkit/connectors/metaMaskConnectConnector.mjs +14 -3
- package/dist/jsx/connectkit/connectors/metaMaskConnectConnector.mjs.map +1 -1
- package/dist/jsx/connectkit/connectors/metaMaskConnectConnector.native.js +14 -3
- package/dist/jsx/connectkit/connectors/metaMaskConnectConnector.native.js.map +1 -1
- package/dist/jsx/phantom/evm.mjs +103 -0
- package/dist/jsx/phantom/evm.mjs.map +1 -0
- package/dist/jsx/phantom/evm.native.js +269 -0
- package/dist/jsx/phantom/evm.native.js.map +1 -0
- package/dist/jsx/phantom/index.mjs +4 -0
- package/dist/jsx/phantom/index.mjs.map +1 -0
- package/dist/jsx/phantom/index.native.js +39 -0
- package/dist/jsx/phantom/index.native.js.map +1 -0
- package/dist/jsx/phantom/urls.mjs +7 -0
- package/dist/jsx/phantom/urls.mjs.map +1 -0
- package/dist/jsx/phantom/urls.native.js +38 -0
- package/dist/jsx/phantom/urls.native.js.map +1 -0
- package/dist/keycloak/login.iife.js +63 -63
- package/package.json +12 -6
- package/src/connectkit/connectors/metaMaskConnectConnector.spec.ts +44 -0
- package/src/connectkit/connectors/metaMaskConnectConnector.ts +30 -2
- package/src/phantom/evm.spec.ts +195 -0
- package/src/phantom/evm.ts +163 -0
- package/src/phantom/index.ts +16 -0
- package/src/phantom/urls.spec.ts +29 -0
- package/src/phantom/urls.ts +16 -0
- package/types/connectkit/connectors/metaMaskConnectConnector.d.ts.map +1 -1
- package/types/phantom/evm.d.ts +76 -0
- package/types/phantom/evm.d.ts.map +1 -0
- package/types/phantom/index.d.ts +3 -0
- package/types/phantom/index.d.ts.map +1 -0
- package/types/phantom/urls.d.ts +11 -0
- package/types/phantom/urls.d.ts.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/web3",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.33.1",
|
|
4
4
|
"description": "Web3 wallet integration for multiplatform.one ecosystem with ConnectKit and wagmi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"connectkit",
|
|
@@ -105,6 +105,12 @@
|
|
|
105
105
|
"import": "./dist/esm/solana.mjs",
|
|
106
106
|
"require": "./dist/cjs/solana.cjs"
|
|
107
107
|
},
|
|
108
|
+
"./phantom": {
|
|
109
|
+
"source": "./src/phantom/index.ts",
|
|
110
|
+
"types": "./types/phantom/index.d.ts",
|
|
111
|
+
"import": "./dist/esm/phantom/index.mjs",
|
|
112
|
+
"require": "./dist/cjs/phantom/index.cjs"
|
|
113
|
+
},
|
|
108
114
|
"./keycloak/login.iife.js": "./dist/keycloak/login.iife.js"
|
|
109
115
|
},
|
|
110
116
|
"publishConfig": {
|
|
@@ -114,10 +120,10 @@
|
|
|
114
120
|
"@aave/account": "^0.2.0",
|
|
115
121
|
"@coinbase/wallet-sdk": "^4.3.6",
|
|
116
122
|
"@metamask/connect-evm": "^2.1.1",
|
|
117
|
-
"@multiplatform.one/connectkit": "7.
|
|
118
|
-
"@multiplatform.one/gpg": "7.
|
|
119
|
-
"@multiplatform.one/platform": "7.
|
|
120
|
-
"@multiplatform.one/theme": "7.
|
|
123
|
+
"@multiplatform.one/connectkit": "7.33.1",
|
|
124
|
+
"@multiplatform.one/gpg": "7.33.1",
|
|
125
|
+
"@multiplatform.one/platform": "7.33.1",
|
|
126
|
+
"@multiplatform.one/theme": "7.33.1",
|
|
121
127
|
"@noble/curves": "^1.9.7",
|
|
122
128
|
"@solana/kit": "^8.3.0",
|
|
123
129
|
"@solana/wallet-adapter-base": "^0.9.27",
|
|
@@ -139,7 +145,7 @@
|
|
|
139
145
|
"wagmi": "^3.7.1"
|
|
140
146
|
},
|
|
141
147
|
"devDependencies": {
|
|
142
|
-
"@multiplatform.one/config": "7.
|
|
148
|
+
"@multiplatform.one/config": "7.33.1",
|
|
143
149
|
"@tamagui/build": "2.7.6",
|
|
144
150
|
"@tanstack/react-query": "^5.100.7",
|
|
145
151
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -339,6 +339,50 @@ describe("metaMaskConnect under wagmi", () => {
|
|
|
339
339
|
expect(request.mock.calls.map(([call]) => call.method)).toContain("personal_sign");
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
+
it("signs under mainnet's scope when the desktop extension sits on a chain the app does not list", async () => {
|
|
343
|
+
// As @metamask/connect-evm's EIP1193Provider: the scope comes from
|
|
344
|
+
// selectedChainId before the first await, and a chain outside
|
|
345
|
+
// supportedNetworks is refused there.
|
|
346
|
+
const scopes: string[] = [];
|
|
347
|
+
const provider = {
|
|
348
|
+
selectedChainId: "0x2105" as string | undefined,
|
|
349
|
+
request: vi.fn(({ method }: { method: string }) => {
|
|
350
|
+
const chainId = provider.selectedChainId!;
|
|
351
|
+
const { api } = createEVMClient.mock.calls[0]![0] as {
|
|
352
|
+
api: { supportedNetworks: Record<string, string> };
|
|
353
|
+
};
|
|
354
|
+
if (!api.supportedNetworks[chainId])
|
|
355
|
+
return Promise.reject(
|
|
356
|
+
new Error(`Chain eip155:${Number(chainId)} is not configured in supportedNetworks`),
|
|
357
|
+
);
|
|
358
|
+
scopes.push(`eip155:${Number(chainId)}`);
|
|
359
|
+
return Promise.resolve(method === "personal_sign" ? "0x5167" : "0xabc");
|
|
360
|
+
}),
|
|
361
|
+
};
|
|
362
|
+
client.getChainId.mockImplementation(() => provider.selectedChainId!);
|
|
363
|
+
client.getProvider.mockReturnValue(provider as never);
|
|
364
|
+
client.connect.mockImplementationOnce(async () => ({ accounts: [first], chainId: "0x1" }));
|
|
365
|
+
const config = createConfig({
|
|
366
|
+
chains: [polygon, mainnet],
|
|
367
|
+
connectors: [metaMaskConnect({ dapp: { name: "test", url: "https://example.test" } })],
|
|
368
|
+
transports: { [polygon.id]: http(), [mainnet.id]: http() },
|
|
369
|
+
multiInjectedProviderDiscovery: false,
|
|
370
|
+
storage: null,
|
|
371
|
+
});
|
|
372
|
+
await connect(config, { connector: config.connectors[0]! });
|
|
373
|
+
(await clientEvents()).chainChanged("0x2105");
|
|
374
|
+
|
|
375
|
+
const signature = await signMessage(config, { message: "prove it" });
|
|
376
|
+
|
|
377
|
+
expect(signature).toBe("0x5167");
|
|
378
|
+
expect(scopes).toEqual(["eip155:1"]);
|
|
379
|
+
expect(provider.selectedChainId).toBe("0x2105");
|
|
380
|
+
await expect(sendTransaction(config, { to: second, value: 1n, gas: 21000n })).rejects.toThrow();
|
|
381
|
+
expect(provider.request.mock.calls.map(([call]) => call.method)).not.toContain(
|
|
382
|
+
"eth_sendTransaction",
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
|
|
342
386
|
it("sends no transaction or typed signature on mainnet for an app that does not list it", async () => {
|
|
343
387
|
const request = vi.fn(async ({ method }: { method: string }) =>
|
|
344
388
|
method === "eth_chainId" ? "0x1" : "0xabc",
|
|
@@ -115,20 +115,45 @@ const offListMethods = new Set([
|
|
|
115
115
|
"wallet_registerOnboarding",
|
|
116
116
|
]);
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* MetaMask Connect scopes a request to the provider's selected chain and refuses
|
|
120
|
+
* one outside supportedNetworks. The desktop extension moves that chain to
|
|
121
|
+
* wherever the user's MetaMask sits, Base say, so personal_sign there never
|
|
122
|
+
* reached the wallet (MPO-494). It goes under mainnet's scope instead, which
|
|
123
|
+
* MetaMask Connect adds to every session. The provider reads the scope before
|
|
124
|
+
* its first await, so the selected chain is put back before anything else runs.
|
|
125
|
+
*/
|
|
126
|
+
function signUnderMainnet(
|
|
127
|
+
provider: EIP1193Provider,
|
|
128
|
+
args: { method: string; params?: unknown },
|
|
129
|
+
): Promise<unknown> {
|
|
130
|
+
const selected = provider.selectedChainId;
|
|
131
|
+
provider.selectedChainId = numberToHex(mainnet.id);
|
|
132
|
+
try {
|
|
133
|
+
return provider.request(args as never);
|
|
134
|
+
} finally {
|
|
135
|
+
provider.selectedChainId = selected;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
118
139
|
function listedChainsOnly(
|
|
119
140
|
provider: EIP1193Provider,
|
|
120
141
|
mm: MetamaskConnectEVM,
|
|
121
142
|
networks: Record<Hex, string>,
|
|
143
|
+
supportedNetworks: Record<Hex, string>,
|
|
122
144
|
): EIP1193Provider {
|
|
123
145
|
const request = (async (args: { method: string; params?: unknown }) => {
|
|
124
146
|
const chainId = mm.getChainId();
|
|
125
|
-
|
|
147
|
+
const current = chainId ? numberToHex(Number(chainId)) : undefined;
|
|
148
|
+
if (current && !networks[current] && !offListMethods.has(args.method))
|
|
126
149
|
throw Object.assign(
|
|
127
150
|
new Error(
|
|
128
151
|
`MetaMask is on chain ${Number(chainId)}, which this app does not list, so ${args.method} was not sent`,
|
|
129
152
|
),
|
|
130
153
|
{ code: 4901 },
|
|
131
154
|
);
|
|
155
|
+
if (current && !supportedNetworks[current] && args.method === "personal_sign")
|
|
156
|
+
return signUnderMainnet(provider, args);
|
|
132
157
|
return provider.request(args as never);
|
|
133
158
|
}) as EIP1193Provider["request"];
|
|
134
159
|
return new Proxy(provider, {
|
|
@@ -383,7 +408,10 @@ export function metaMaskConnect(parameters: MetaMaskConnectParameters) {
|
|
|
383
408
|
const mm = await claim();
|
|
384
409
|
const provider = mm.getProvider();
|
|
385
410
|
if (guarded?.provider !== provider)
|
|
386
|
-
guarded = {
|
|
411
|
+
guarded = {
|
|
412
|
+
provider,
|
|
413
|
+
wrapped: listedChainsOnly(provider, mm, networks, supportedNetworks),
|
|
414
|
+
};
|
|
387
415
|
return guarded.wrapped;
|
|
388
416
|
},
|
|
389
417
|
async isAuthorized() {
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { baseSepolia } from "viem/chains";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
connectPhantomEvm,
|
|
5
|
+
discoverPhantomEthereumProvider,
|
|
6
|
+
openInPhantom,
|
|
7
|
+
phantomEthereumProvider,
|
|
8
|
+
phantomEvmRoute,
|
|
9
|
+
PhantomNotInjectedError,
|
|
10
|
+
type PhantomEthereumProvider,
|
|
11
|
+
} from "./evm";
|
|
12
|
+
|
|
13
|
+
const account = "0x1111111111111111111111111111111111111111";
|
|
14
|
+
|
|
15
|
+
function fakeProvider(chainId: number, opts: { unknownChain?: boolean } = {}) {
|
|
16
|
+
let current = chainId;
|
|
17
|
+
let known = !opts.unknownChain;
|
|
18
|
+
const calls: { method: string; params?: unknown }[] = [];
|
|
19
|
+
const request = vi.fn(async ({ method, params }: { method: string; params?: unknown }) => {
|
|
20
|
+
calls.push({ method, params });
|
|
21
|
+
switch (method) {
|
|
22
|
+
case "eth_requestAccounts":
|
|
23
|
+
case "eth_accounts":
|
|
24
|
+
return [account];
|
|
25
|
+
case "eth_chainId":
|
|
26
|
+
return `0x${current.toString(16)}`;
|
|
27
|
+
case "wallet_switchEthereumChain": {
|
|
28
|
+
if (!known) throw Object.assign(new Error("Unrecognized chain"), { code: 4902 });
|
|
29
|
+
current = Number((params as [{ chainId: string }])[0].chainId);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
case "wallet_addEthereumChain":
|
|
33
|
+
known = true;
|
|
34
|
+
return null;
|
|
35
|
+
default:
|
|
36
|
+
throw new Error(`unexpected ${method}`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const provider = { isPhantom: true, request, on: vi.fn(), removeListener: vi.fn() };
|
|
40
|
+
return { provider: provider as unknown as PhantomEthereumProvider, calls };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("phantom evm", () => {
|
|
44
|
+
it("finds window.phantom.ethereum, then a Phantom window.ethereum, and nothing else", () => {
|
|
45
|
+
const own = { isPhantom: true } as unknown as PhantomEthereumProvider;
|
|
46
|
+
const other = { isMetaMask: true } as unknown as PhantomEthereumProvider;
|
|
47
|
+
expect(phantomEthereumProvider({ phantom: { ethereum: own }, ethereum: other })).toBe(own);
|
|
48
|
+
expect(phantomEthereumProvider({ ethereum: own })).toBe(own);
|
|
49
|
+
expect(phantomEthereumProvider({ ethereum: other })).toBeUndefined();
|
|
50
|
+
expect(phantomEvmRoute({ ethereum: other })).toBe("browse");
|
|
51
|
+
expect(phantomEvmRoute({ phantom: { ethereum: own } })).toBe("injected");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("takes Phantom's EIP-6963 announcement", async () => {
|
|
55
|
+
const provider = { isPhantom: true } as unknown as PhantomEthereumProvider;
|
|
56
|
+
const target = new EventTarget();
|
|
57
|
+
target.addEventListener("eip6963:requestProvider", () => {
|
|
58
|
+
target.dispatchEvent(
|
|
59
|
+
Object.assign(new Event("eip6963:announceProvider"), {
|
|
60
|
+
detail: { info: { rdns: "io.metamask" }, provider: {} },
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
target.dispatchEvent(
|
|
64
|
+
Object.assign(new Event("eip6963:announceProvider"), {
|
|
65
|
+
detail: { info: { rdns: "app.phantom" }, provider },
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
const win = {
|
|
70
|
+
addEventListener: target.addEventListener.bind(target),
|
|
71
|
+
removeEventListener: target.removeEventListener.bind(target),
|
|
72
|
+
dispatchEvent: target.dispatchEvent.bind(target),
|
|
73
|
+
};
|
|
74
|
+
await expect(discoverPhantomEthereumProvider({ window: win as never })).resolves.toBe(provider);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("resolves undefined when nothing announces", async () => {
|
|
78
|
+
const target = new EventTarget();
|
|
79
|
+
const win = {
|
|
80
|
+
addEventListener: target.addEventListener.bind(target),
|
|
81
|
+
removeEventListener: target.removeEventListener.bind(target),
|
|
82
|
+
dispatchEvent: target.dispatchEvent.bind(target),
|
|
83
|
+
};
|
|
84
|
+
await expect(
|
|
85
|
+
discoverPhantomEthereumProvider({ window: win as never, timeout: 5 }),
|
|
86
|
+
).resolves.toBeUndefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("opens this page in Phantom's browser synchronously", () => {
|
|
90
|
+
const assign = vi.fn();
|
|
91
|
+
const link = openInPhantom({
|
|
92
|
+
window: {
|
|
93
|
+
location: {
|
|
94
|
+
href: "https://dev.promanager.vip/u/buy?mobile=1",
|
|
95
|
+
origin: "https://dev.promanager.vip",
|
|
96
|
+
assign,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
expect(link).toBe(
|
|
101
|
+
"https://phantom.com/ul/browse/https%3A%2F%2Fdev.promanager.vip%2Fu%2Fbuy%3Fmobile%3D1?ref=https%3A%2F%2Fdev.promanager.vip",
|
|
102
|
+
);
|
|
103
|
+
expect(assign).toHaveBeenCalledWith(link);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("connects, switches to Base Sepolia, and returns a client on it", async () => {
|
|
107
|
+
const { provider, calls } = fakeProvider(1);
|
|
108
|
+
const client = await connectPhantomEvm({ chain: baseSepolia, provider });
|
|
109
|
+
expect(client.address.toLowerCase()).toBe(account);
|
|
110
|
+
expect(client.chainId).toBe(84532);
|
|
111
|
+
expect(calls.map((c) => c.method)).toEqual([
|
|
112
|
+
"eth_requestAccounts",
|
|
113
|
+
"eth_chainId",
|
|
114
|
+
"wallet_switchEthereumChain",
|
|
115
|
+
]);
|
|
116
|
+
expect(calls[2].params).toEqual([{ chainId: "0x14a34" }]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("adds the chain when Phantom does not know it", async () => {
|
|
120
|
+
const { provider, calls } = fakeProvider(1, { unknownChain: true });
|
|
121
|
+
await connectPhantomEvm({ chain: baseSepolia, provider });
|
|
122
|
+
expect(calls.map((c) => c.method)).toEqual([
|
|
123
|
+
"eth_requestAccounts",
|
|
124
|
+
"eth_chainId",
|
|
125
|
+
"wallet_switchEthereumChain",
|
|
126
|
+
"wallet_addEthereumChain",
|
|
127
|
+
"wallet_switchEthereumChain",
|
|
128
|
+
]);
|
|
129
|
+
expect((calls[3].params as [{ chainId: string; rpcUrls: string[] }])[0]).toMatchObject({
|
|
130
|
+
chainId: "0x14a34",
|
|
131
|
+
rpcUrls: [...baseSepolia.rpcUrls.default.http],
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("skips the switch when already on the chain", async () => {
|
|
136
|
+
const { provider, calls } = fakeProvider(84532);
|
|
137
|
+
await connectPhantomEvm({ chain: baseSepolia, provider });
|
|
138
|
+
expect(calls.map((c) => c.method)).toEqual(["eth_requestAccounts", "eth_chainId"]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("sends an ERC-20 transfer to Phantom as eth_sendTransaction", async () => {
|
|
142
|
+
const { provider } = fakeProvider(84532);
|
|
143
|
+
const sent: unknown[] = [];
|
|
144
|
+
const request = provider.request as unknown as ReturnType<typeof vi.fn>;
|
|
145
|
+
const base = request.getMockImplementation() as (a: {
|
|
146
|
+
method: string;
|
|
147
|
+
params?: unknown;
|
|
148
|
+
}) => Promise<unknown>;
|
|
149
|
+
request.mockImplementation(async (args: { method: string; params?: unknown }) => {
|
|
150
|
+
if (args.method === "eth_sendTransaction") {
|
|
151
|
+
sent.push(args.params);
|
|
152
|
+
return `0x${"ab".repeat(32)}`;
|
|
153
|
+
}
|
|
154
|
+
return base(args);
|
|
155
|
+
});
|
|
156
|
+
const client = await connectPhantomEvm({ chain: baseSepolia, provider });
|
|
157
|
+
const hash = await client.walletClient.writeContract({
|
|
158
|
+
address: "0xc6fe3521644e0f5439fe345f64d372cd6ed37491",
|
|
159
|
+
abi: [
|
|
160
|
+
{
|
|
161
|
+
type: "function",
|
|
162
|
+
name: "transfer",
|
|
163
|
+
stateMutability: "nonpayable",
|
|
164
|
+
inputs: [
|
|
165
|
+
{ name: "to", type: "address" },
|
|
166
|
+
{ name: "amount", type: "uint256" },
|
|
167
|
+
],
|
|
168
|
+
outputs: [{ type: "bool" }],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
functionName: "transfer",
|
|
172
|
+
args: ["0x2222222222222222222222222222222222222222", 10n ** 18n],
|
|
173
|
+
gas: 100000n,
|
|
174
|
+
});
|
|
175
|
+
expect(hash).toBe(`0x${"ab".repeat(32)}`);
|
|
176
|
+
expect(sent[0]).toEqual([
|
|
177
|
+
expect.objectContaining({
|
|
178
|
+
from: account,
|
|
179
|
+
to: "0xc6fe3521644e0f5439fe345f64d372cd6ed37491",
|
|
180
|
+
data: expect.stringMatching(/^0xa9059cbb0{24}2{40}/),
|
|
181
|
+
}),
|
|
182
|
+
]);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("says to reopen in Phantom when nothing is injected", async () => {
|
|
186
|
+
vi.stubGlobal("phantom", undefined);
|
|
187
|
+
try {
|
|
188
|
+
await expect(connectPhantomEvm({ chain: baseSepolia })).rejects.toBeInstanceOf(
|
|
189
|
+
PhantomNotInjectedError,
|
|
190
|
+
);
|
|
191
|
+
} finally {
|
|
192
|
+
vi.unstubAllGlobals();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { numberToHex, type Chain, type EIP1193Provider, type PublicClient } from "viem";
|
|
2
|
+
import { evmClient, type EvmClient } from "../clients/evm";
|
|
3
|
+
import { phantomBrowseUrl } from "./urls";
|
|
4
|
+
|
|
5
|
+
export type PhantomEthereumProvider = EIP1193Provider & { isPhantom?: boolean };
|
|
6
|
+
|
|
7
|
+
interface PhantomWindow {
|
|
8
|
+
phantom?: { ethereum?: PhantomEthereumProvider };
|
|
9
|
+
ethereum?: PhantomEthereumProvider;
|
|
10
|
+
location?: { href: string; origin: string; assign(url: string): void };
|
|
11
|
+
addEventListener?: (type: string, listener: (event: Event) => void) => void;
|
|
12
|
+
removeEventListener?: (type: string, listener: (event: Event) => void) => void;
|
|
13
|
+
dispatchEvent?: (event: Event) => boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const phantomRdns = "app.phantom";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Phantom's injected EVM provider: `window.phantom.ethereum`, else a
|
|
20
|
+
* `window.ethereum` that says it is Phantom. Present in Phantom's desktop
|
|
21
|
+
* extension and its in-app browser, never in Chrome or Safari on a phone.
|
|
22
|
+
*/
|
|
23
|
+
export function phantomEthereumProvider(
|
|
24
|
+
win: PhantomWindow = globalThis as PhantomWindow,
|
|
25
|
+
): PhantomEthereumProvider | undefined {
|
|
26
|
+
const own = win.phantom?.ethereum;
|
|
27
|
+
if (own?.isPhantom) return own;
|
|
28
|
+
if (win.ethereum?.isPhantom) return win.ethereum;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* phantomEthereumProvider, or Phantom's EIP-6963 announcement (rdns
|
|
34
|
+
* `app.phantom`) when the provider is not on `window` yet. Resolves undefined
|
|
35
|
+
* after `timeout` ms.
|
|
36
|
+
*/
|
|
37
|
+
export function discoverPhantomEthereumProvider(
|
|
38
|
+
options: { timeout?: number; window?: PhantomWindow } = {},
|
|
39
|
+
): Promise<PhantomEthereumProvider | undefined> {
|
|
40
|
+
const win = options.window ?? (globalThis as PhantomWindow);
|
|
41
|
+
const found = phantomEthereumProvider(win);
|
|
42
|
+
if (found || !win.addEventListener || !win.dispatchEvent) return Promise.resolve(found);
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const onAnnounce = (event: Event) => {
|
|
45
|
+
const detail = (
|
|
46
|
+
event as CustomEvent<{ info?: { rdns?: string }; provider?: PhantomEthereumProvider }>
|
|
47
|
+
).detail;
|
|
48
|
+
if (detail?.info?.rdns !== phantomRdns || !detail.provider) return;
|
|
49
|
+
done(detail.provider);
|
|
50
|
+
};
|
|
51
|
+
const timer = setTimeout(() => done(phantomEthereumProvider(win)), options.timeout ?? 500);
|
|
52
|
+
const done = (provider: PhantomEthereumProvider | undefined) => {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
win.removeEventListener?.("eip6963:announceProvider", onAnnounce);
|
|
55
|
+
resolve(provider);
|
|
56
|
+
};
|
|
57
|
+
win.addEventListener?.("eip6963:announceProvider", onAnnounce);
|
|
58
|
+
win.dispatchEvent?.(new Event("eip6963:requestProvider"));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* How this page reaches Phantom's EVM wallet: `injected` when Phantom's
|
|
64
|
+
* provider is here (extension or Phantom's browser), `browse` when the page
|
|
65
|
+
* must first reopen inside Phantom with openInPhantom.
|
|
66
|
+
*/
|
|
67
|
+
export function phantomEvmRoute(win: PhantomWindow = globalThis as PhantomWindow) {
|
|
68
|
+
return phantomEthereumProvider(win) ? ("injected" as const) : ("browse" as const);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface OpenInPhantomOptions {
|
|
72
|
+
/** The page to open in Phantom's browser. Defaults to this page. */
|
|
73
|
+
url?: string;
|
|
74
|
+
/** The requesting app. Defaults to this page's origin. */
|
|
75
|
+
ref?: string;
|
|
76
|
+
/** Defaults to `location.assign`. */
|
|
77
|
+
navigate?: (url: string) => void;
|
|
78
|
+
base?: string;
|
|
79
|
+
window?: PhantomWindow;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Reopens the page inside Phantom's in-app browser. Call it straight from the
|
|
84
|
+
* tap handler with nothing awaited first: mobile browsers only follow a link
|
|
85
|
+
* into another app without a prompt inside the tap's user activation.
|
|
86
|
+
* Returns the link, for an `<a href>` instead.
|
|
87
|
+
*/
|
|
88
|
+
export function openInPhantom(options: OpenInPhantomOptions = {}): string {
|
|
89
|
+
const win = options.window ?? (globalThis as PhantomWindow);
|
|
90
|
+
const url = options.url ?? win.location?.href;
|
|
91
|
+
const ref = options.ref ?? win.location?.origin ?? url;
|
|
92
|
+
if (!url || !ref) throw new Error("openInPhantom: no page URL here; pass `url` and `ref`");
|
|
93
|
+
const link = phantomBrowseUrl(url, ref, { base: options.base });
|
|
94
|
+
(options.navigate ?? ((target: string) => win.location?.assign(target)))(link);
|
|
95
|
+
return link;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ConnectPhantomEvmOptions {
|
|
99
|
+
chain: Chain;
|
|
100
|
+
/** Defaults to phantomEthereumProvider(). */
|
|
101
|
+
provider?: PhantomEthereumProvider;
|
|
102
|
+
publicClient?: PublicClient;
|
|
103
|
+
rpcUrl?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export class PhantomNotInjectedError extends Error {
|
|
107
|
+
constructor() {
|
|
108
|
+
super(
|
|
109
|
+
"Phantom's EVM provider is not on this page: on a phone, reopen it in Phantom with openInPhantom()",
|
|
110
|
+
);
|
|
111
|
+
this.name = "PhantomNotInjectedError";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const errorCode = (error: unknown) =>
|
|
116
|
+
(error as { code?: unknown; cause?: { code?: unknown } } | undefined)?.code ??
|
|
117
|
+
(error as { cause?: { code?: unknown } } | undefined)?.cause?.code;
|
|
118
|
+
|
|
119
|
+
async function ensureChain(provider: PhantomEthereumProvider, chain: Chain) {
|
|
120
|
+
const current = await provider.request({ method: "eth_chainId" });
|
|
121
|
+
if (Number(current) === chain.id) return;
|
|
122
|
+
const chainId = numberToHex(chain.id);
|
|
123
|
+
try {
|
|
124
|
+
await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId }] });
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (errorCode(error) !== 4902) throw error;
|
|
127
|
+
await provider.request({
|
|
128
|
+
method: "wallet_addEthereumChain",
|
|
129
|
+
params: [
|
|
130
|
+
{
|
|
131
|
+
chainId,
|
|
132
|
+
chainName: chain.name,
|
|
133
|
+
nativeCurrency: chain.nativeCurrency,
|
|
134
|
+
rpcUrls: [...chain.rpcUrls.default.http],
|
|
135
|
+
blockExplorerUrls: chain.blockExplorers ? [chain.blockExplorers.default.url] : undefined,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId }] });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Connects Phantom's injected EVM wallet, moves it to `chain`, and returns
|
|
145
|
+
* the mpo EVM client on it, so `erc20.transfer` and `writeContract` go to
|
|
146
|
+
* Phantom for approval. Testnets such as Base Sepolia need Phantom's Testnet
|
|
147
|
+
* Mode on.
|
|
148
|
+
*/
|
|
149
|
+
export async function connectPhantomEvm(options: ConnectPhantomEvmOptions): Promise<EvmClient> {
|
|
150
|
+
const provider = options.provider ?? phantomEthereumProvider();
|
|
151
|
+
if (!provider) throw new PhantomNotInjectedError();
|
|
152
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
153
|
+
const account = accounts[0];
|
|
154
|
+
if (!account) throw new Error("Phantom returned no EVM account");
|
|
155
|
+
await ensureChain(provider, options.chain);
|
|
156
|
+
return evmClient({
|
|
157
|
+
provider,
|
|
158
|
+
account,
|
|
159
|
+
chain: options.chain,
|
|
160
|
+
publicClient: options.publicClient,
|
|
161
|
+
rpcUrl: options.rpcUrl,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// `@multiplatform.one/web3/phantom`: Phantom's EVM wallet from a phone's own
|
|
2
|
+
// browser. Phantom's deeplinks carry no EVM methods, so the page reopens in
|
|
3
|
+
// Phantom's in-app browser (openInPhantom) and connects the provider Phantom
|
|
4
|
+
// injects there (connectPhantomEvm).
|
|
5
|
+
export {
|
|
6
|
+
connectPhantomEvm,
|
|
7
|
+
discoverPhantomEthereumProvider,
|
|
8
|
+
openInPhantom,
|
|
9
|
+
phantomEthereumProvider,
|
|
10
|
+
phantomEvmRoute,
|
|
11
|
+
PhantomNotInjectedError,
|
|
12
|
+
type ConnectPhantomEvmOptions,
|
|
13
|
+
type OpenInPhantomOptions,
|
|
14
|
+
type PhantomEthereumProvider,
|
|
15
|
+
} from "./evm";
|
|
16
|
+
export { phantomBrowseUrl, phantomUniversalLinkBase } from "./urls";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { phantomBrowseUrl } from "./urls";
|
|
3
|
+
|
|
4
|
+
describe("phantomBrowseUrl", () => {
|
|
5
|
+
it("builds the browse link Phantom documents", () => {
|
|
6
|
+
expect(
|
|
7
|
+
phantomBrowseUrl(
|
|
8
|
+
"https://magiceden.io/item-details/8yjN8iRuoiYiKW487cnW9vn6mLBR5E8aCNKsBRmTP9vN",
|
|
9
|
+
"https://magiceden.io",
|
|
10
|
+
),
|
|
11
|
+
).toBe(
|
|
12
|
+
"https://phantom.com/ul/browse/https%3A%2F%2Fmagiceden.io%2Fitem-details%2F8yjN8iRuoiYiKW487cnW9vn6mLBR5E8aCNKsBRmTP9vN?ref=https%3A%2F%2Fmagiceden.io",
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("keeps a page's own query inside the browse link", () => {
|
|
17
|
+
expect(
|
|
18
|
+
phantomBrowseUrl(
|
|
19
|
+
"https://dev.promanager.vip/u/buy?mobile=1&x=a b",
|
|
20
|
+
"https://dev.promanager.vip",
|
|
21
|
+
{
|
|
22
|
+
base: "https://phantom.app/ul/",
|
|
23
|
+
},
|
|
24
|
+
),
|
|
25
|
+
).toBe(
|
|
26
|
+
"https://phantom.app/ul/browse/https%3A%2F%2Fdev.promanager.vip%2Fu%2Fbuy%3Fmobile%3D1%26x%3Da%20b?ref=https%3A%2F%2Fdev.promanager.vip",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Phantom's universal-link host. `https://phantom.app/ul` still answers; Phantom asks new code to use this one. */
|
|
2
|
+
export const phantomUniversalLinkBase = "https://phantom.com/ul";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Opens `url` inside Phantom's in-app browser, where Phantom injects
|
|
6
|
+
* `window.phantom.ethereum`. Phantom's deeplinks carry no EVM methods, so
|
|
7
|
+
* this is how a phone's browser reaches its EVM wallet.
|
|
8
|
+
*/
|
|
9
|
+
export function phantomBrowseUrl(
|
|
10
|
+
url: string,
|
|
11
|
+
ref: string,
|
|
12
|
+
options: { base?: string } = {},
|
|
13
|
+
): string {
|
|
14
|
+
const base = (options.base ?? phantomUniversalLinkBase).replace(/\/+$/, "");
|
|
15
|
+
return `${base}/browse/${encodeURIComponent(url)}?ref=${encodeURIComponent(ref)}`;
|
|
16
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metaMaskConnectConnector.d.ts","sourceRoot":"","sources":["../../../src/connectkit/connectors/metaMaskConnectConnector.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,uBAAuB,CAAC;AAEjF,OAAO,EAA2B,KAAK,iBAAiB,EAAmB,MAAM,OAAO,CAAC;AACzF,OAAO,EAEL,KAAK,KAAK,EAEV,KAAK,GAAG,EAKT,MAAM,MAAM,CAAC;AAKd,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAC3C,CAAC;AAEF,KAAK,eAAe,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAGxD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,GACzC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAQrB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,EAC7B,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,GACzC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAOrB;
|
|
1
|
+
{"version":3,"file":"metaMaskConnectConnector.d.ts","sourceRoot":"","sources":["../../../src/connectkit/connectors/metaMaskConnectConnector.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,uBAAuB,CAAC;AAEjF,OAAO,EAA2B,KAAK,iBAAiB,EAAmB,MAAM,OAAO,CAAC;AACzF,OAAO,EAEL,KAAK,KAAK,EAEV,KAAK,GAAG,EAKT,MAAM,MAAM,CAAC;AAKd,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAC3C,CAAC;AAEF,KAAK,eAAe,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAGxD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,GACzC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAQrB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,EAC7B,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,GACzC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAOrB;AAsGD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAE1D;AAOD,wGAAwG;AACxG,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAK9F;AAqBD;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAIxC;AAgFD,oFAAoF;AACpF,wBAAgB,0BAA0B,IAAI,IAAI,CAIjD;AAWD,wBAAgB,eAAe,CAAC,UAAU,EAAE,yBAAyB,wFAwJpE;yBAxJe,eAAe"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type Chain, type EIP1193Provider, type PublicClient } from "viem";
|
|
2
|
+
import { type EvmClient } from "../clients/evm";
|
|
3
|
+
export type PhantomEthereumProvider = EIP1193Provider & {
|
|
4
|
+
isPhantom?: boolean;
|
|
5
|
+
};
|
|
6
|
+
interface PhantomWindow {
|
|
7
|
+
phantom?: {
|
|
8
|
+
ethereum?: PhantomEthereumProvider;
|
|
9
|
+
};
|
|
10
|
+
ethereum?: PhantomEthereumProvider;
|
|
11
|
+
location?: {
|
|
12
|
+
href: string;
|
|
13
|
+
origin: string;
|
|
14
|
+
assign(url: string): void;
|
|
15
|
+
};
|
|
16
|
+
addEventListener?: (type: string, listener: (event: Event) => void) => void;
|
|
17
|
+
removeEventListener?: (type: string, listener: (event: Event) => void) => void;
|
|
18
|
+
dispatchEvent?: (event: Event) => boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Phantom's injected EVM provider: `window.phantom.ethereum`, else a
|
|
22
|
+
* `window.ethereum` that says it is Phantom. Present in Phantom's desktop
|
|
23
|
+
* extension and its in-app browser, never in Chrome or Safari on a phone.
|
|
24
|
+
*/
|
|
25
|
+
export declare function phantomEthereumProvider(win?: PhantomWindow): PhantomEthereumProvider | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* phantomEthereumProvider, or Phantom's EIP-6963 announcement (rdns
|
|
28
|
+
* `app.phantom`) when the provider is not on `window` yet. Resolves undefined
|
|
29
|
+
* after `timeout` ms.
|
|
30
|
+
*/
|
|
31
|
+
export declare function discoverPhantomEthereumProvider(options?: {
|
|
32
|
+
timeout?: number;
|
|
33
|
+
window?: PhantomWindow;
|
|
34
|
+
}): Promise<PhantomEthereumProvider | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* How this page reaches Phantom's EVM wallet: `injected` when Phantom's
|
|
37
|
+
* provider is here (extension or Phantom's browser), `browse` when the page
|
|
38
|
+
* must first reopen inside Phantom with openInPhantom.
|
|
39
|
+
*/
|
|
40
|
+
export declare function phantomEvmRoute(win?: PhantomWindow): "injected" | "browse";
|
|
41
|
+
export interface OpenInPhantomOptions {
|
|
42
|
+
/** The page to open in Phantom's browser. Defaults to this page. */
|
|
43
|
+
url?: string;
|
|
44
|
+
/** The requesting app. Defaults to this page's origin. */
|
|
45
|
+
ref?: string;
|
|
46
|
+
/** Defaults to `location.assign`. */
|
|
47
|
+
navigate?: (url: string) => void;
|
|
48
|
+
base?: string;
|
|
49
|
+
window?: PhantomWindow;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reopens the page inside Phantom's in-app browser. Call it straight from the
|
|
53
|
+
* tap handler with nothing awaited first: mobile browsers only follow a link
|
|
54
|
+
* into another app without a prompt inside the tap's user activation.
|
|
55
|
+
* Returns the link, for an `<a href>` instead.
|
|
56
|
+
*/
|
|
57
|
+
export declare function openInPhantom(options?: OpenInPhantomOptions): string;
|
|
58
|
+
export interface ConnectPhantomEvmOptions {
|
|
59
|
+
chain: Chain;
|
|
60
|
+
/** Defaults to phantomEthereumProvider(). */
|
|
61
|
+
provider?: PhantomEthereumProvider;
|
|
62
|
+
publicClient?: PublicClient;
|
|
63
|
+
rpcUrl?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare class PhantomNotInjectedError extends Error {
|
|
66
|
+
constructor();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Connects Phantom's injected EVM wallet, moves it to `chain`, and returns
|
|
70
|
+
* the mpo EVM client on it, so `erc20.transfer` and `writeContract` go to
|
|
71
|
+
* Phantom for approval. Testnets such as Base Sepolia need Phantom's Testnet
|
|
72
|
+
* Mode on.
|
|
73
|
+
*/
|
|
74
|
+
export declare function connectPhantomEvm(options: ConnectPhantomEvmOptions): Promise<EvmClient>;
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=evm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evm.d.ts","sourceRoot":"","sources":["../../src/phantom/evm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,MAAM,CAAC;AACxF,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3D,MAAM,MAAM,uBAAuB,GAAG,eAAe,GAAG;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhF,UAAU,aAAa;IACrB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,uBAAuB,CAAA;KAAE,CAAC;IACjD,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACvE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,IAAI,CAAC;IAC5E,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,IAAI,CAAC;IAC/E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAC3C;AAID;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,aAA2C,GAC/C,uBAAuB,GAAG,SAAS,CAKrC;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,aAAa,CAAA;CAAO,GACzD,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CAqB9C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,aAA2C,yBAE/E;AAED,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,CAQxE;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,KAAK,CAAC;IACb,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,uBAAwB,SAAQ,KAAK;;CAOjD;AA8BD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,SAAS,CAAC,CAc7F"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { connectPhantomEvm, discoverPhantomEthereumProvider, openInPhantom, phantomEthereumProvider, phantomEvmRoute, PhantomNotInjectedError, type ConnectPhantomEvmOptions, type OpenInPhantomOptions, type PhantomEthereumProvider, } from "./evm";
|
|
2
|
+
export { phantomBrowseUrl, phantomUniversalLinkBase } from "./urls";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|