Skip to content

Commit 6e69eee

Browse files
abstract wallet support
1 parent 07af647 commit 6e69eee

File tree

37 files changed

+1036
-376
lines changed

37 files changed

+1036
-376
lines changed
65.3 KB
Loading

packages/thirdweb/scripts/wallets/extra-wallets.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,33 @@
9191
"native": null,
9292
"universal": null
9393
}
94+
},
95+
{
96+
"id": "abstract",
97+
"name": "Abstract Global Wallet",
98+
"homepage": "https://abs.xyz/",
99+
"image_id": "abstract.png",
100+
"app": {
101+
"browser": null,
102+
"ios": null,
103+
"android": null,
104+
"mac": null,
105+
"windows": null,
106+
"linux": null,
107+
"chrome": null,
108+
"firefox": null,
109+
"safari": null,
110+
"edge": null,
111+
"opera": null
112+
},
113+
"rdns": "xyz.abs",
114+
"mobile": {
115+
"native": null,
116+
"universal": null
117+
},
118+
"desktop": {
119+
"native": null,
120+
"universal": null
121+
}
94122
}
95123
]
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { trackConnect } from "../../analytics/track/connect.js";
2+
import type { Chain } from "../../chains/types.js";
3+
import { getCachedChainIfExists } from "../../chains/utils.js";
4+
import {
5+
autoConnectEip1193Wallet,
6+
connectEip1193Wallet,
7+
} from "../../wallets/injected/index.js";
8+
import type { Account, Wallet } from "../../wallets/interfaces/wallet.js";
9+
import { createWalletEmitter } from "../../wallets/wallet-emitter.js";
10+
import type { WalletId } from "../../wallets/wallet-types.js";
11+
import type { EIP1193Provider } from "./types.js";
12+
13+
export type FromEip1193AdapterOptions = {
14+
provider: EIP1193Provider | (() => Promise<EIP1193Provider>);
15+
walletId?: WalletId;
16+
};
17+
18+
/**
19+
* Converts an EIP1193 provider to a Thirdweb wallet.
20+
*
21+
* @param options - The options for converting an EIP1193 provider to a Thirdweb wallet.
22+
* @returns A Thirdweb wallet.
23+
* @example
24+
* ```ts
25+
* import { EIP1193 } from "thirdweb/wallets";
26+
* const wallet = EIP1193.fromProvider({ provider });
27+
*
28+
* // ... now you can use wallet with ConnectButton, useConnect, etc
29+
* ```
30+
* @walletUtils
31+
*/
32+
export function fromProvider(options: FromEip1193AdapterOptions): Wallet {
33+
const id: WalletId = options.walletId ?? "adapter";
34+
const emitter = createWalletEmitter();
35+
let account: Account | undefined = undefined;
36+
let chain: Chain | undefined = undefined;
37+
let provider: EIP1193Provider | undefined = undefined;
38+
const getProvider = async () => {
39+
if (!provider) {
40+
provider =
41+
typeof options.provider === "function"
42+
? await options.provider()
43+
: options.provider;
44+
}
45+
return provider;
46+
};
47+
48+
const unsubscribeChain = emitter.subscribe("chainChanged", (newChain) => {
49+
chain = newChain;
50+
});
51+
52+
function reset() {
53+
account = undefined;
54+
chain = undefined;
55+
}
56+
57+
let handleDisconnect = async () => {};
58+
59+
const unsubscribeDisconnect = emitter.subscribe("disconnect", () => {
60+
reset();
61+
unsubscribeChain();
62+
unsubscribeDisconnect();
63+
});
64+
65+
emitter.subscribe("accountChanged", (_account) => {
66+
account = _account;
67+
});
68+
69+
let handleSwitchChain: (chain: Chain) => Promise<void> = async () => {
70+
throw new Error("Not implemented");
71+
};
72+
73+
return {
74+
id: options.walletId as WalletId,
75+
subscribe: emitter.subscribe,
76+
getConfig: () => undefined,
77+
getChain() {
78+
if (!chain) {
79+
return undefined;
80+
}
81+
82+
chain = getCachedChainIfExists(chain.id) || chain;
83+
return chain;
84+
},
85+
getAccount: () => account,
86+
connect: async (connectOptions) => {
87+
const [connectedAccount, connectedChain, doDisconnect, doSwitchChain] =
88+
await connectEip1193Wallet({
89+
id,
90+
provider: await getProvider(),
91+
client: connectOptions.client,
92+
chain: connectOptions.chain,
93+
emitter,
94+
});
95+
// set the states
96+
account = connectedAccount;
97+
chain = connectedChain;
98+
handleDisconnect = doDisconnect;
99+
handleSwitchChain = doSwitchChain;
100+
trackConnect({
101+
client: connectOptions.client,
102+
walletType: id,
103+
walletAddress: account.address,
104+
});
105+
// return account
106+
return account;
107+
},
108+
autoConnect: async (connectOptions) => {
109+
const [connectedAccount, connectedChain, doDisconnect, doSwitchChain] =
110+
await autoConnectEip1193Wallet({
111+
id,
112+
provider: await getProvider(),
113+
emitter,
114+
chain: connectOptions.chain,
115+
client: connectOptions.client,
116+
});
117+
// set the states
118+
account = connectedAccount;
119+
chain = connectedChain;
120+
handleDisconnect = doDisconnect;
121+
handleSwitchChain = doSwitchChain;
122+
trackConnect({
123+
client: connectOptions.client,
124+
walletType: id,
125+
walletAddress: account.address,
126+
});
127+
// return account
128+
return account;
129+
},
130+
disconnect: async () => {
131+
reset();
132+
await handleDisconnect();
133+
},
134+
switchChain: (c) => handleSwitchChain(c),
135+
};
136+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export {
2+
type FromEip1193AdapterOptions,
3+
fromProvider,
4+
} from "./from-eip1193.js";
5+
6+
export {
7+
type ToEip1193ProviderOptions,
8+
toProvider,
9+
} from "./to-eip1193.js";
10+
11+
export type { EIP1193Provider } from "./types.js";
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import type { Account } from "viem/accounts";
2+
3+
import type { Chain } from "../../chains/types.js";
4+
import type { ThirdwebClient } from "../../client/client.js";
5+
import { getRpcClient } from "../../rpc/rpc.js";
6+
import { estimateGas } from "../../transaction/actions/estimate-gas.js";
7+
import { sendTransaction } from "../../transaction/actions/send-transaction.js";
8+
import { prepareTransaction } from "../../transaction/prepare-transaction.js";
9+
import type { Wallet } from "../../wallets/interfaces/wallet.js";
10+
import type { EIP1193Provider } from "./types.js";
11+
12+
export type ToEip1193ProviderOptions = {
13+
wallet: Wallet;
14+
chain: Chain;
15+
client: ThirdwebClient;
16+
connectOverride?: (wallet: Wallet) => Promise<Account>;
17+
};
18+
19+
/**
20+
* Converts an EIP1193 provider to a Thirdweb wallet.
21+
*
22+
* @param options - The options for converting an EIP1193 provider to a Thirdweb wallet.
23+
* @returns A Thirdweb wallet.
24+
* @example
25+
* ```ts
26+
* import { EIP1193 } from "thirdweb/wallets";
27+
* const provider = EIP1193.toProvider({ wallet, chain, client });
28+
*
29+
* // ... now you can use this providers with third party libraries
30+
* ```
31+
* @walletUtils
32+
*/
33+
export function toProvider(options: ToEip1193ProviderOptions): EIP1193Provider {
34+
const { chain, client, wallet, connectOverride } = options;
35+
const rpcClient = getRpcClient({ client, chain });
36+
return {
37+
on: wallet.subscribe,
38+
removeListener: () => {
39+
// should invoke the return fn from subscribe instead
40+
},
41+
request: async (request) => {
42+
if (request.method === "eth_sendTransaction") {
43+
const account = wallet.getAccount();
44+
if (!account) {
45+
throw new Error("Account not connected");
46+
}
47+
const result = await sendTransaction({
48+
transaction: prepareTransaction({
49+
...request.params[0],
50+
chain,
51+
client,
52+
}),
53+
account: account,
54+
});
55+
return result.transactionHash;
56+
}
57+
if (request.method === "eth_estimateGas") {
58+
const account = wallet.getAccount();
59+
if (!account) {
60+
throw new Error("Account not connected");
61+
}
62+
return estimateGas({
63+
transaction: prepareTransaction({
64+
...request.params[0],
65+
chain,
66+
client,
67+
}),
68+
account,
69+
});
70+
}
71+
if (request.method === "personal_sign") {
72+
const account = wallet.getAccount();
73+
if (!account) {
74+
throw new Error("Account not connected");
75+
}
76+
return account.signMessage({
77+
message: {
78+
raw: request.params[0],
79+
},
80+
});
81+
}
82+
if (request.method === "eth_signTypedData_v4") {
83+
const account = wallet.getAccount();
84+
if (!account) {
85+
throw new Error("Account not connected");
86+
}
87+
const data = JSON.parse(request.params[1]);
88+
return account.signTypedData(data);
89+
}
90+
if (request.method === "eth_accounts") {
91+
const account = wallet.getAccount();
92+
if (!account) {
93+
throw new Error("Account not connected");
94+
}
95+
return [account.address];
96+
}
97+
if (request.method === "eth_requestAccounts") {
98+
const account = connectOverride
99+
? await connectOverride(wallet)
100+
: await wallet.connect({
101+
client,
102+
});
103+
if (!account) {
104+
throw new Error("Unable to connect wallet");
105+
}
106+
return [account.address];
107+
}
108+
return rpcClient(request);
109+
},
110+
};
111+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// loose interface on purpose to adapt to any version of viem, ethers, ox, web3js, etc
2+
export type EIP1193Provider = {
3+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
4+
on(event: any, listener: (params: any) => any): void;
5+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
6+
removeListener(event: any, listener: (params: any) => any): void;
7+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
8+
request: (params: any) => Promise<any>;
9+
};

packages/thirdweb/src/analytics/track/transaction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type TransactionEvent = {
99
ecosystem?: Ecosystem;
1010
transactionHash?: string;
1111
walletAddress?: string;
12-
walletType?: WalletId;
12+
walletType?: WalletId | ({} & string);
1313
chainId?: number;
1414
contractAddress?: string;
1515
functionName?: string;

packages/thirdweb/src/exports/wallets.native.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ export type {
134134
WalletConnectSession,
135135
} from "../wallets/wallet-connect/receiver/types.js";
136136

137+
// eip1193
138+
export * as EIP1193 from "../adapters/eip1193/index.js";
139+
137140
// NOT SUPPORTED
138141

139142
export const injectedProvider = () => {

packages/thirdweb/src/exports/wallets.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ export type {
144144
WalletConnectSession,
145145
} from "../wallets/wallet-connect/receiver/types.js";
146146

147+
// eip1193
148+
export * as EIP1193 from "../adapters/eip1193/index.js";
149+
147150
// WEB ONLY EXPORTS
148151

149152
// injected

packages/thirdweb/src/react/core/hooks/wallets/useAdminWallet.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useConnectedWallets } from "./useConnectedWallets.js";
55
* Get the admin wallet for the active wallet
66
* Useful for smart wallets to get the underlying personal account
77
* @returns The admin wallet for the active wallet, or the active wallet if it doesn't have an admin account
8+
* @walletConnection
89
*/
910
export function useAdminWallet() {
1011
const activeWallet = useActiveWallet();

0 commit comments

Comments
 (0)