@cartridge/controller 0.10.0-alpha.1 → 0.10.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.
Files changed (45) hide show
  1. package/.turbo/turbo-build$colon$deps.log +19 -19
  2. package/.turbo/turbo-build.log +17 -17
  3. package/dist/controller.d.ts +2 -2
  4. package/dist/index.js +2412 -1104
  5. package/dist/index.js.map +1 -1
  6. package/dist/node/index.cjs +1 -1
  7. package/dist/node/index.cjs.map +1 -1
  8. package/dist/node/index.js +1 -1
  9. package/dist/node/index.js.map +1 -1
  10. package/dist/{provider-CKE81veU.js → provider-DSw1EyU9.js} +45 -44
  11. package/dist/provider-DSw1EyU9.js.map +1 -0
  12. package/dist/session.js +24 -24
  13. package/dist/session.js.map +1 -1
  14. package/dist/stats.html +1 -1
  15. package/dist/types.d.ts +20 -1
  16. package/dist/utils/solana/connection.d.ts +19 -0
  17. package/dist/utils/solana/index.d.ts +37 -0
  18. package/dist/utils/solana/spl-token.d.ts +6 -0
  19. package/dist/wallets/argent/index.d.ts +4 -0
  20. package/dist/wallets/base/index.d.ts +5 -18
  21. package/dist/wallets/braavos/index.d.ts +4 -0
  22. package/dist/wallets/ethereum-base.d.ts +29 -0
  23. package/dist/wallets/index.d.ts +2 -0
  24. package/dist/wallets/metamask/index.d.ts +5 -19
  25. package/dist/wallets/rabby/index.d.ts +5 -19
  26. package/dist/wallets/types.d.ts +1 -0
  27. package/package.json +5 -7
  28. package/src/__tests__/controllerDefaults.test.ts +5 -5
  29. package/src/__tests__/parseChainId.test.ts +6 -2
  30. package/src/controller.ts +8 -10
  31. package/src/types.ts +23 -1
  32. package/src/utils/solana/connection.ts +78 -0
  33. package/src/utils/solana/index.ts +143 -0
  34. package/src/utils/solana/spl-token.ts +66 -0
  35. package/src/wallets/argent/index.ts +36 -0
  36. package/src/wallets/base/index.ts +5 -303
  37. package/src/wallets/braavos/index.ts +36 -0
  38. package/src/wallets/ethereum-base.ts +446 -0
  39. package/src/wallets/index.ts +2 -0
  40. package/src/wallets/metamask/index.ts +5 -334
  41. package/src/wallets/phantom/index.ts +5 -1
  42. package/src/wallets/rabby/index.ts +5 -332
  43. package/src/wallets/types.ts +1 -0
  44. package/vite.config.js +0 -2
  45. package/dist/provider-CKE81veU.js.map +0 -1
@@ -0,0 +1,66 @@
1
+ import * as sol from "micro-sol-signer";
2
+ import bs58 from "bs58";
3
+
4
+ // SPL Token Program IDs
5
+ export const TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
6
+ export const ASSOCIATED_TOKEN_PROGRAM_ID =
7
+ "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
8
+
9
+ // Helper to get associated token address
10
+ export function getAssociatedTokenAddress(mint: string, owner: string): string {
11
+ // Derive the associated token account address
12
+ // This is a simplified implementation for compatibility
13
+ const seeds = [
14
+ bs58.decode(owner),
15
+ bs58.decode(TOKEN_PROGRAM_ID),
16
+ bs58.decode(mint),
17
+ ];
18
+
19
+ // For SPL Token ATAs, this is deterministic but simplified
20
+ // In production, use proper PDA derivation
21
+ const combined = Buffer.concat(seeds.map((s) => Buffer.from(s)));
22
+ const hash = Buffer.from(combined).slice(0, 32);
23
+ return bs58.encode(hash);
24
+ }
25
+
26
+ // Create associated token account instruction
27
+ export function createAssociatedTokenAccountInstruction(
28
+ payer: string,
29
+ associatedToken: string,
30
+ owner: string,
31
+ mint: string,
32
+ ): sol.Instruction {
33
+ // Create the instruction to create an associated token account
34
+ return {
35
+ program: ASSOCIATED_TOKEN_PROGRAM_ID,
36
+ keys: [
37
+ { address: payer, sign: true, write: true },
38
+ { address: associatedToken, sign: false, write: true },
39
+ { address: owner, sign: false, write: false },
40
+ { address: mint, sign: false, write: false },
41
+ {
42
+ address: "11111111111111111111111111111111",
43
+ sign: false,
44
+ write: false,
45
+ }, // System program
46
+ { address: TOKEN_PROGRAM_ID, sign: false, write: false },
47
+ ],
48
+ data: new Uint8Array(0), // No data for create instruction
49
+ };
50
+ }
51
+
52
+ // Create transfer instruction
53
+ export function createTransferInstruction(
54
+ source: string,
55
+ destination: string,
56
+ owner: string,
57
+ amount: bigint,
58
+ ): sol.Instruction {
59
+ // Use micro-sol-signer's token transfer instruction
60
+ return sol.token.transfer({
61
+ source,
62
+ destination,
63
+ owner,
64
+ amount,
65
+ });
66
+ }
@@ -13,6 +13,8 @@ export class ArgentWallet implements WalletAdapter {
13
13
  private wallet: StarknetWindowObject | undefined = undefined;
14
14
  private account: string | undefined = undefined;
15
15
  private connectedAccounts: string[] = [];
16
+ private accountChangeListener: ((accounts?: string[]) => void) | undefined =
17
+ undefined;
16
18
 
17
19
  isAvailable(): boolean {
18
20
  return typeof window !== "undefined" && !!window.starknet_argentX;
@@ -58,9 +60,12 @@ export class ArgentWallet implements WalletAdapter {
58
60
  throw new Error("No accounts found");
59
61
  }
60
62
 
63
+ this.removeAccountChangeListener();
64
+
61
65
  this.wallet = wallet;
62
66
  this.account = accounts[0];
63
67
  this.connectedAccounts = accounts;
68
+ this.setupAccountChangeListener();
64
69
  return { success: true, wallet: this.type, account: this.account };
65
70
  } catch (error) {
66
71
  console.error(`Error connecting to Argent:`, error);
@@ -175,4 +180,35 @@ export class ArgentWallet implements WalletAdapter {
175
180
  error: "waitForTransaction not supported for Argent wallet",
176
181
  };
177
182
  }
183
+
184
+ private setupAccountChangeListener(): void {
185
+ if (!this.wallet) return;
186
+
187
+ this.accountChangeListener = (accounts: string[] | undefined) => {
188
+ if (accounts && accounts.length > 0) {
189
+ this.account = accounts[0];
190
+ this.connectedAccounts = accounts;
191
+ } else {
192
+ this.account = undefined;
193
+ this.connectedAccounts = [];
194
+ }
195
+ };
196
+
197
+ // Listen for account changes
198
+ this.wallet.on("accountsChanged", this.accountChangeListener);
199
+ }
200
+
201
+ private removeAccountChangeListener(): void {
202
+ if (this.wallet && this.accountChangeListener) {
203
+ this.wallet.off("accountsChanged", this.accountChangeListener);
204
+ this.accountChangeListener = undefined;
205
+ }
206
+ }
207
+
208
+ disconnect(): void {
209
+ this.removeAccountChangeListener();
210
+ this.wallet = undefined;
211
+ this.account = undefined;
212
+ this.connectedAccounts = [];
213
+ }
178
214
  }
@@ -1,306 +1,8 @@
1
- import { getAddress } from "ethers/address";
2
- import { createStore, EIP6963ProviderDetail } from "mipd";
3
- import {
4
- ExternalPlatform,
5
- ExternalWallet,
6
- ExternalWalletResponse,
7
- ExternalWalletType,
8
- WalletAdapter,
9
- } from "../types";
10
- import { chainIdToPlatform } from "../platform";
1
+ import { ExternalWalletType } from "../types";
2
+ import { EthereumWalletBase } from "../ethereum-base";
11
3
 
12
- const BASE_RDNS = "com.coinbase.wallet";
13
-
14
- export class BaseWallet implements WalletAdapter {
4
+ export class BaseWallet extends EthereumWalletBase {
15
5
  readonly type: ExternalWalletType = "base";
16
- platform: ExternalPlatform | undefined;
17
-
18
- private account: string | undefined = undefined;
19
- private store = createStore();
20
- private provider: EIP6963ProviderDetail | undefined;
21
- private connectedAccounts: string[] = [];
22
-
23
- constructor() {
24
- this.provider = this.store
25
- .getProviders()
26
- .find((provider) => provider.info.rdns === BASE_RDNS);
27
-
28
- this.provider?.provider
29
- .request({
30
- method: "eth_accounts",
31
- })
32
- .then((accounts) => {
33
- this.connectedAccounts = accounts.map(getAddress);
34
- if (accounts.length > 0) {
35
- this.account = getAddress(accounts?.[0]);
36
- }
37
- });
38
-
39
- this.provider?.provider
40
- .request({
41
- method: "eth_chainId",
42
- })
43
- .then((chainId) => {
44
- this.platform = chainIdToPlatform(chainId);
45
- });
46
-
47
- this.provider?.provider?.on("chainChanged", (chainId: string) => {
48
- this.platform = chainIdToPlatform(chainId);
49
- });
50
-
51
- this.provider?.provider?.on("accountsChanged", (accounts: string[]) => {
52
- if (accounts) {
53
- this.connectedAccounts = accounts.map((account) => getAddress(account));
54
- this.account = getAddress(accounts?.[0]);
55
- }
56
- });
57
- }
58
-
59
- isAvailable(): boolean {
60
- return typeof window !== "undefined" && !!this.provider;
61
- }
62
-
63
- getInfo(): ExternalWallet {
64
- const available = this.isAvailable();
65
-
66
- return {
67
- type: this.type,
68
- available,
69
- version: available ? window.ethereum?.version || "Unknown" : undefined,
70
- chainId: available ? window.ethereum?.chainId : undefined,
71
- name: "Base Wallet",
72
- platform: this.platform,
73
- connectedAccounts: this.connectedAccounts,
74
- };
75
- }
76
-
77
- getConnectedAccounts(): string[] {
78
- return this.connectedAccounts;
79
- }
80
-
81
- async connect(address?: string): Promise<ExternalWalletResponse<any>> {
82
- if (address && this.connectedAccounts.includes(getAddress(address))) {
83
- this.account = getAddress(address);
84
- }
85
-
86
- if (this.account) {
87
- return { success: true, wallet: this.type, account: this.account };
88
- }
89
-
90
- try {
91
- if (!this.isAvailable()) {
92
- throw new Error("Base Wallet is not available");
93
- }
94
-
95
- const accounts = await this.provider?.provider.request({
96
- method: "eth_requestAccounts",
97
- });
98
- if (accounts && accounts.length > 0) {
99
- this.account = getAddress(accounts[0]);
100
- this.connectedAccounts = accounts.map(getAddress);
101
- return { success: true, wallet: this.type, account: this.account };
102
- }
103
-
104
- throw new Error("No accounts found");
105
- } catch (error) {
106
- console.error(`Error connecting to Base Wallet:`, error);
107
- return {
108
- success: false,
109
- wallet: this.type,
110
- error: (error as Error).message || "Unknown error",
111
- };
112
- }
113
- }
114
-
115
- async signMessage(
116
- message: `0x${string}`,
117
- address?: string,
118
- ): Promise<ExternalWalletResponse<any>> {
119
- try {
120
- if (!this.isAvailable() || !this.account) {
121
- throw new Error("Base Wallet is not connected");
122
- }
123
-
124
- const result = await this.provider?.provider.request({
125
- method: "personal_sign",
126
- params: [message, address || this.account] as any,
127
- });
128
-
129
- return { success: true, wallet: this.type, result };
130
- } catch (error) {
131
- console.error(`Error signing message with Base Wallet:`, error);
132
- return {
133
- success: false,
134
- wallet: this.type,
135
- error: (error as Error).message || "Unknown error",
136
- };
137
- }
138
- }
139
-
140
- async signTypedData(data: any): Promise<ExternalWalletResponse<any>> {
141
- try {
142
- if (!this.isAvailable() || !this.account) {
143
- throw new Error("Base Wallet is not connected");
144
- }
145
-
146
- const provider = this.provider?.provider;
147
- if (!provider) {
148
- throw new Error("Base Wallet is not connected");
149
- }
150
-
151
- const result = await provider.request({
152
- method: "eth_signTypedData_v4",
153
- params: [this.account, JSON.stringify(data)] as any,
154
- });
155
-
156
- return { success: true, wallet: this.type, result };
157
- } catch (error) {
158
- console.error(`Error signing typed data with Base Wallet:`, error);
159
- return {
160
- success: false,
161
- wallet: this.type,
162
- error: (error as Error).message || "Unknown error",
163
- };
164
- }
165
- }
166
-
167
- async sendTransaction(txn: any): Promise<ExternalWalletResponse<any>> {
168
- try {
169
- if (!this.isAvailable() || !this.account) {
170
- throw new Error("Base Wallet is not connected");
171
- }
172
-
173
- const provider = this.provider?.provider;
174
- if (!provider) {
175
- throw new Error("Base Wallet is not connected");
176
- }
177
-
178
- const result = await provider.request({
179
- method: "eth_sendTransaction",
180
- params: [txn],
181
- });
182
-
183
- return { success: true, wallet: this.type, result };
184
- } catch (error) {
185
- console.error(`Error sending transaction with Base Wallet:`, error);
186
- return {
187
- success: false,
188
- wallet: this.type,
189
- error: (error as Error).message || "Unknown error",
190
- };
191
- }
192
- }
193
-
194
- async switchChain(chainId: string): Promise<boolean> {
195
- try {
196
- if (!this.isAvailable()) {
197
- throw new Error("Base Wallet is not available");
198
- }
199
-
200
- const provider = this.provider?.provider;
201
- if (!provider) {
202
- throw new Error("Base Wallet is not connected");
203
- }
204
-
205
- try {
206
- await provider.request({
207
- method: "wallet_switchEthereumChain",
208
- params: [{ chainId }],
209
- });
210
-
211
- this.platform = chainIdToPlatform(chainId);
212
- return true;
213
- } catch (error) {
214
- if ((error as any).code === 4902) {
215
- console.warn("Chain not added to Base Wallet");
216
- }
217
- throw error;
218
- }
219
- } catch (error) {
220
- console.error(`Error switching chain for Base Wallet:`, error);
221
- return false;
222
- }
223
- }
224
-
225
- async getBalance(
226
- tokenAddress?: string,
227
- ): Promise<ExternalWalletResponse<any>> {
228
- try {
229
- if (!this.isAvailable() || !this.account) {
230
- throw new Error("Base Wallet is not connected");
231
- }
232
-
233
- if (tokenAddress) {
234
- return {
235
- success: false,
236
- wallet: this.type,
237
- error: "Not implemented for ERC20",
238
- };
239
- } else {
240
- const provider = this.provider?.provider;
241
- if (!provider) {
242
- throw new Error("Base Wallet is not connected");
243
- }
244
-
245
- const balance = await provider.request({
246
- method: "eth_getBalance",
247
- params: [this.account, "latest"] as any,
248
- });
249
- return { success: true, wallet: this.type, result: balance };
250
- }
251
- } catch (error) {
252
- console.error(`Error getting balance from Base Wallet:`, error);
253
- return {
254
- success: false,
255
- wallet: this.type,
256
- error: (error as Error).message || "Unknown error",
257
- };
258
- }
259
- }
260
-
261
- async waitForTransaction(
262
- txHash: string,
263
- timeoutMs: number = 60000,
264
- ): Promise<ExternalWalletResponse<any>> {
265
- try {
266
- if (!this.isAvailable()) {
267
- throw new Error("Base Wallet is not connected");
268
- }
269
-
270
- const provider = this.provider?.provider;
271
- if (!provider) {
272
- throw new Error("Base Wallet is not connected");
273
- }
274
-
275
- const startTime = Date.now();
276
- const pollInterval = 1000; // 1 second
277
-
278
- while (Date.now() - startTime < timeoutMs) {
279
- const receipt = await provider.request({
280
- method: "eth_getTransactionReceipt",
281
- params: [txHash as `0x${string}`],
282
- });
283
-
284
- if (receipt) {
285
- return {
286
- success: true,
287
- wallet: this.type,
288
- result: receipt,
289
- };
290
- }
291
-
292
- // Wait before polling again
293
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
294
- }
295
-
296
- throw new Error("Transaction confirmation timed out");
297
- } catch (error) {
298
- console.error(`Error waiting for transaction with Base Wallet:`, error);
299
- return {
300
- success: false,
301
- wallet: this.type,
302
- error: (error as Error).message || "Unknown error",
303
- };
304
- }
305
- }
6
+ readonly rdns = "com.coinbase.wallet";
7
+ readonly displayName = "Base Wallet";
306
8
  }
@@ -13,6 +13,8 @@ export class BraavosWallet implements WalletAdapter {
13
13
  private wallet: StarknetWindowObject | undefined = undefined;
14
14
  private account: string | undefined = undefined;
15
15
  private connectedAccounts: string[] = [];
16
+ private accountChangeListener: ((accounts?: string[]) => void) | undefined =
17
+ undefined;
16
18
 
17
19
  isAvailable(): boolean {
18
20
  return typeof window !== "undefined" && !!window.starknet_braavos;
@@ -58,9 +60,12 @@ export class BraavosWallet implements WalletAdapter {
58
60
  throw new Error("No accounts found");
59
61
  }
60
62
 
63
+ this.removeAccountChangeListener();
64
+
61
65
  this.wallet = wallet;
62
66
  this.account = accounts[0];
63
67
  this.connectedAccounts = accounts;
68
+ this.setupAccountChangeListener();
64
69
  return { success: true, wallet: this.type, account: this.account };
65
70
  } catch (error) {
66
71
  console.error(`Error connecting to Braavos:`, error);
@@ -174,4 +179,35 @@ export class BraavosWallet implements WalletAdapter {
174
179
  error: "waitForTransaction not supported for Braavos wallet",
175
180
  };
176
181
  }
182
+
183
+ private setupAccountChangeListener(): void {
184
+ if (!this.wallet) return;
185
+
186
+ this.accountChangeListener = (accounts: string[] | undefined) => {
187
+ if (accounts && accounts.length > 0) {
188
+ this.account = accounts[0];
189
+ this.connectedAccounts = accounts;
190
+ } else {
191
+ this.account = undefined;
192
+ this.connectedAccounts = [];
193
+ }
194
+ };
195
+
196
+ // Listen for account changes
197
+ this.wallet.on("accountsChanged", this.accountChangeListener);
198
+ }
199
+
200
+ private removeAccountChangeListener(): void {
201
+ if (this.wallet && this.accountChangeListener) {
202
+ this.wallet.off("accountsChanged", this.accountChangeListener);
203
+ this.accountChangeListener = undefined;
204
+ }
205
+ }
206
+
207
+ disconnect(): void {
208
+ this.removeAccountChangeListener();
209
+ this.wallet = undefined;
210
+ this.account = undefined;
211
+ this.connectedAccounts = [];
212
+ }
177
213
  }