@meshconnect/uwc-bridge-parent 1.1.1 → 1.2.0
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/BaseWalletAdapter.d.ts +27 -0
- package/dist/BaseWalletAdapter.d.ts.map +1 -0
- package/dist/BaseWalletAdapter.js +205 -0
- package/dist/BaseWalletAdapter.js.map +1 -0
- package/dist/BaseWalletAdapter.test.d.ts +2 -0
- package/dist/BaseWalletAdapter.test.d.ts.map +1 -0
- package/dist/BaseWalletAdapter.test.js +299 -0
- package/dist/BaseWalletAdapter.test.js.map +1 -0
- package/dist/BridgeParent.d.ts.map +1 -1
- package/dist/BridgeParent.js +140 -1
- package/dist/BridgeParent.js.map +1 -1
- package/dist/BridgeParent.test.js +118 -1
- package/dist/BridgeParent.test.js.map +1 -1
- package/package.json +1 -1
- package/src/BaseWalletAdapter.test.ts +371 -0
- package/src/BaseWalletAdapter.ts +295 -0
- package/src/BridgeParent.test.ts +152 -1
- package/src/BridgeParent.ts +169 -1
package/src/BridgeParent.test.ts
CHANGED
|
@@ -14,7 +14,13 @@ vi.mock('@wallet-standard/app', () => ({
|
|
|
14
14
|
|
|
15
15
|
// Mock @solana/wallet-adapter-base
|
|
16
16
|
vi.mock('@solana/wallet-adapter-base', () => ({
|
|
17
|
-
isWalletAdapterCompatibleStandardWallet: vi.fn().mockReturnValue(false)
|
|
17
|
+
isWalletAdapterCompatibleStandardWallet: vi.fn().mockReturnValue(false),
|
|
18
|
+
WalletReadyState: {
|
|
19
|
+
Installed: 'Installed',
|
|
20
|
+
NotDetected: 'NotDetected',
|
|
21
|
+
Loadable: 'Loadable',
|
|
22
|
+
Unsupported: 'Unsupported'
|
|
23
|
+
}
|
|
18
24
|
}))
|
|
19
25
|
|
|
20
26
|
// Mock @solana/wallet-standard-wallet-adapter-base
|
|
@@ -29,6 +35,20 @@ vi.mock('@solana/web3.js', () => ({
|
|
|
29
35
|
VersionedTransaction: { deserialize: vi.fn() }
|
|
30
36
|
}))
|
|
31
37
|
|
|
38
|
+
// Hoisted mock for BaseWalletAdapter - direct reference avoids restoreAllMocks issues
|
|
39
|
+
const baseAdapterMock = vi.hoisted(() => ({
|
|
40
|
+
readyState: 'NotDetected' as string,
|
|
41
|
+
shouldThrow: false,
|
|
42
|
+
fn: null as ReturnType<typeof vi.fn> | null
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
baseAdapterMock.fn = vi.fn()
|
|
46
|
+
|
|
47
|
+
// Mock ./BaseWalletAdapter
|
|
48
|
+
vi.mock('./BaseWalletAdapter', () => ({
|
|
49
|
+
BaseWalletAdapter: baseAdapterMock.fn
|
|
50
|
+
}))
|
|
51
|
+
|
|
32
52
|
describe('BridgeParent', () => {
|
|
33
53
|
let mockIframe: HTMLIFrameElement
|
|
34
54
|
|
|
@@ -394,6 +414,137 @@ describe('BridgeParent', () => {
|
|
|
394
414
|
})
|
|
395
415
|
})
|
|
396
416
|
|
|
417
|
+
describe('Base Wallet detection', () => {
|
|
418
|
+
beforeEach(async () => {
|
|
419
|
+
baseAdapterMock.readyState = 'NotDetected'
|
|
420
|
+
baseAdapterMock.shouldThrow = false
|
|
421
|
+
|
|
422
|
+
// Re-set mocks that vi.restoreAllMocks() may have cleared
|
|
423
|
+
const { getWallets } = await import('@wallet-standard/app')
|
|
424
|
+
vi.mocked(getWallets).mockReturnValue({
|
|
425
|
+
get: () => [],
|
|
426
|
+
on: vi.fn(),
|
|
427
|
+
register: vi.fn()
|
|
428
|
+
} as any)
|
|
429
|
+
|
|
430
|
+
// Re-set BaseWalletAdapter mock - must use 'function' (not arrow) for 'new' compatibility
|
|
431
|
+
baseAdapterMock.fn!.mockImplementation(function () {
|
|
432
|
+
if (baseAdapterMock.shouldThrow) throw new Error('init failed')
|
|
433
|
+
return {
|
|
434
|
+
readyState: baseAdapterMock.readyState,
|
|
435
|
+
name: 'Base Wallet',
|
|
436
|
+
icon: 'data:image/svg+xml;base64,test',
|
|
437
|
+
url: 'https://example.com',
|
|
438
|
+
publicKey: null,
|
|
439
|
+
connecting: false,
|
|
440
|
+
connected: false,
|
|
441
|
+
supportedTransactionVersions: new Set(['legacy', 0]),
|
|
442
|
+
connect: vi.fn(),
|
|
443
|
+
disconnect: vi.fn(),
|
|
444
|
+
sendTransaction: vi.fn(),
|
|
445
|
+
signTransaction: vi.fn(),
|
|
446
|
+
signAllTransactions: vi.fn(),
|
|
447
|
+
signMessage: vi.fn()
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
it('should include Base Wallet when detected as Installed', async () => {
|
|
453
|
+
const Comlink = await import('comlink')
|
|
454
|
+
const { BridgeParent } = await import('./BridgeParent')
|
|
455
|
+
|
|
456
|
+
baseAdapterMock.readyState = 'Installed'
|
|
457
|
+
|
|
458
|
+
new BridgeParent(mockIframe)
|
|
459
|
+
await vi.advanceTimersByTimeAsync(200)
|
|
460
|
+
|
|
461
|
+
const exposedAPI = vi.mocked(Comlink.expose).mock.calls[0]?.[0] as any
|
|
462
|
+
const baseWallet = exposedAPI.walletStandardWallets.find(
|
|
463
|
+
(w: any) => w.name === 'Base Wallet'
|
|
464
|
+
)
|
|
465
|
+
expect(baseWallet).toBeDefined()
|
|
466
|
+
expect(baseWallet.uuid).toBe('base-wallet-traditional')
|
|
467
|
+
expect(baseWallet.chains).toEqual(['solana:mainnet'])
|
|
468
|
+
expect(baseWallet.features).toEqual(['traditional-adapter'])
|
|
469
|
+
expect(baseWallet.adapter).toBeDefined()
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it('should include Base Wallet when detected as Loadable', async () => {
|
|
473
|
+
const Comlink = await import('comlink')
|
|
474
|
+
const { BridgeParent } = await import('./BridgeParent')
|
|
475
|
+
|
|
476
|
+
baseAdapterMock.readyState = 'Loadable'
|
|
477
|
+
|
|
478
|
+
new BridgeParent(mockIframe)
|
|
479
|
+
await vi.advanceTimersByTimeAsync(200)
|
|
480
|
+
|
|
481
|
+
const exposedAPI = vi.mocked(Comlink.expose).mock.calls[0]?.[0] as any
|
|
482
|
+
const baseWallet = exposedAPI.walletStandardWallets.find(
|
|
483
|
+
(w: any) => w.name === 'Base Wallet'
|
|
484
|
+
)
|
|
485
|
+
expect(baseWallet).toBeDefined()
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
it('should not include Base Wallet when not detected', async () => {
|
|
489
|
+
const Comlink = await import('comlink')
|
|
490
|
+
const { BridgeParent } = await import('./BridgeParent')
|
|
491
|
+
|
|
492
|
+
new BridgeParent(mockIframe)
|
|
493
|
+
await vi.advanceTimersByTimeAsync(200)
|
|
494
|
+
|
|
495
|
+
const exposedAPI = vi.mocked(Comlink.expose).mock.calls[0]?.[0] as any
|
|
496
|
+
const baseWallet = exposedAPI.walletStandardWallets.find(
|
|
497
|
+
(w: any) => w.name === 'Base Wallet'
|
|
498
|
+
)
|
|
499
|
+
expect(baseWallet).toBeUndefined()
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
it('should skip Base Wallet if name already in Wallet Standard wallets', async () => {
|
|
503
|
+
const { getWallets } = await import('@wallet-standard/app')
|
|
504
|
+
const Comlink = await import('comlink')
|
|
505
|
+
const { BridgeParent } = await import('./BridgeParent')
|
|
506
|
+
|
|
507
|
+
const existingWallet = {
|
|
508
|
+
name: 'Base Wallet',
|
|
509
|
+
chains: ['solana:mainnet'],
|
|
510
|
+
features: { 'standard:connect': {} },
|
|
511
|
+
accounts: []
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
vi.mocked(getWallets).mockReturnValue({
|
|
515
|
+
get: () => [existingWallet] as any,
|
|
516
|
+
on: vi.fn(),
|
|
517
|
+
register: vi.fn()
|
|
518
|
+
} as any)
|
|
519
|
+
|
|
520
|
+
baseAdapterMock.readyState = 'Installed'
|
|
521
|
+
|
|
522
|
+
new BridgeParent(mockIframe)
|
|
523
|
+
await vi.advanceTimersByTimeAsync(200)
|
|
524
|
+
|
|
525
|
+
const exposedAPI = vi.mocked(Comlink.expose).mock.calls[0]?.[0] as any
|
|
526
|
+
const baseWallets = exposedAPI.walletStandardWallets.filter(
|
|
527
|
+
(w: any) => w.name === 'Base Wallet'
|
|
528
|
+
)
|
|
529
|
+
expect(baseWallets).toHaveLength(1)
|
|
530
|
+
expect(baseWallets[0].features).not.toContain('traditional-adapter')
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
it('should handle BaseWalletAdapter constructor errors gracefully', async () => {
|
|
534
|
+
const Comlink = await import('comlink')
|
|
535
|
+
const { BridgeParent } = await import('./BridgeParent')
|
|
536
|
+
|
|
537
|
+
baseAdapterMock.shouldThrow = true
|
|
538
|
+
|
|
539
|
+
new BridgeParent(mockIframe)
|
|
540
|
+
await vi.advanceTimersByTimeAsync(200)
|
|
541
|
+
|
|
542
|
+
const exposedAPI = vi.mocked(Comlink.expose).mock.calls[0]?.[0] as any
|
|
543
|
+
expect(exposedAPI).toBeDefined()
|
|
544
|
+
expect(exposedAPI.walletStandardWallets).toEqual([])
|
|
545
|
+
})
|
|
546
|
+
})
|
|
547
|
+
|
|
397
548
|
describe('destroy', () => {
|
|
398
549
|
it('should clear all state', async () => {
|
|
399
550
|
const { BridgeParent } = await import('./BridgeParent')
|
package/src/BridgeParent.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import * as Comlink from 'comlink'
|
|
2
2
|
import type { Wallet } from '@wallet-standard/base'
|
|
3
3
|
import { getWallets } from '@wallet-standard/app'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isWalletAdapterCompatibleStandardWallet,
|
|
6
|
+
WalletReadyState
|
|
7
|
+
} from '@solana/wallet-adapter-base'
|
|
5
8
|
import { Connection, Transaction, VersionedTransaction } from '@solana/web3.js'
|
|
6
9
|
import { StandardWalletAdapter } from '@solana/wallet-standard-wallet-adapter-base'
|
|
10
|
+
import { BaseWalletAdapter } from './BaseWalletAdapter'
|
|
7
11
|
|
|
8
12
|
export interface EthereumProvider {
|
|
9
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -375,6 +379,170 @@ export class BridgeParent {
|
|
|
375
379
|
walletNames.add(wallet.name)
|
|
376
380
|
}
|
|
377
381
|
}
|
|
382
|
+
|
|
383
|
+
// Add Base Wallet via traditional adapter (detects window.coinbaseSolana)
|
|
384
|
+
try {
|
|
385
|
+
const baseAdapter = new BaseWalletAdapter()
|
|
386
|
+
const isDetected =
|
|
387
|
+
baseAdapter.readyState === WalletReadyState.Installed ||
|
|
388
|
+
baseAdapter.readyState === WalletReadyState.Loadable
|
|
389
|
+
|
|
390
|
+
if (isDetected && !walletNames.has(baseAdapter.name)) {
|
|
391
|
+
solanaWallets.push({
|
|
392
|
+
uuid: `${baseAdapter.name}-traditional`
|
|
393
|
+
.toLowerCase()
|
|
394
|
+
.replace(/\s+/g, '-'),
|
|
395
|
+
name: baseAdapter.name,
|
|
396
|
+
chains: ['solana:mainnet'],
|
|
397
|
+
features: ['traditional-adapter'],
|
|
398
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
399
|
+
// @ts-ignore
|
|
400
|
+
adapter: {
|
|
401
|
+
name: baseAdapter.name,
|
|
402
|
+
url: baseAdapter.url,
|
|
403
|
+
// @ts-expect-error icon is string but type expects template literal
|
|
404
|
+
icon: baseAdapter.icon,
|
|
405
|
+
readyState: baseAdapter.readyState,
|
|
406
|
+
// @ts-expect-error publicKey is a Comlink proxy, not a real PublicKey
|
|
407
|
+
publicKey: Comlink.proxy({
|
|
408
|
+
get value() {
|
|
409
|
+
return baseAdapter.publicKey
|
|
410
|
+
},
|
|
411
|
+
toBase58: async () => {
|
|
412
|
+
return baseAdapter.publicKey?.toBase58()
|
|
413
|
+
},
|
|
414
|
+
toJSON: async () => {
|
|
415
|
+
return baseAdapter.publicKey?.toJSON()
|
|
416
|
+
},
|
|
417
|
+
toBytes: async () => {
|
|
418
|
+
return baseAdapter.publicKey?.toBytes()
|
|
419
|
+
},
|
|
420
|
+
toBuffer: async () => {
|
|
421
|
+
return baseAdapter.publicKey?.toBuffer()
|
|
422
|
+
},
|
|
423
|
+
toString: async () => {
|
|
424
|
+
return baseAdapter.publicKey?.toString()
|
|
425
|
+
}
|
|
426
|
+
}),
|
|
427
|
+
connecting: baseAdapter.connecting,
|
|
428
|
+
connected: baseAdapter.connected,
|
|
429
|
+
supportedTransactionVersions:
|
|
430
|
+
baseAdapter.supportedTransactionVersions,
|
|
431
|
+
connect: async () => await baseAdapter.connect(),
|
|
432
|
+
disconnect: async () => await baseAdapter.disconnect(),
|
|
433
|
+
sendTransaction: async (transaction, connection, options) => {
|
|
434
|
+
try {
|
|
435
|
+
return await baseAdapter.sendTransaction(
|
|
436
|
+
transaction,
|
|
437
|
+
connection,
|
|
438
|
+
options
|
|
439
|
+
)
|
|
440
|
+
} catch {
|
|
441
|
+
throw new Error('Failed to send transaction')
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
signTransaction: async transaction => {
|
|
445
|
+
return await baseAdapter.signTransaction(transaction)
|
|
446
|
+
},
|
|
447
|
+
signAllTransactions: async transactions => {
|
|
448
|
+
return await baseAdapter.signAllTransactions(transactions)
|
|
449
|
+
},
|
|
450
|
+
signMessage: async message => {
|
|
451
|
+
return await baseAdapter.signMessage(message)
|
|
452
|
+
},
|
|
453
|
+
customFunctions: [
|
|
454
|
+
'sendSerializedTransaction',
|
|
455
|
+
'signAllSerializedTransactions',
|
|
456
|
+
'signSerializedTransaction'
|
|
457
|
+
],
|
|
458
|
+
sendSerializedTransaction: async (
|
|
459
|
+
transaction: ArrayBufferLike,
|
|
460
|
+
rpcUrl: string
|
|
461
|
+
) => {
|
|
462
|
+
const connection = new Connection(rpcUrl)
|
|
463
|
+
const uint8Array = new Uint8Array(transaction)
|
|
464
|
+
|
|
465
|
+
let deserializedTx: Transaction | VersionedTransaction
|
|
466
|
+
try {
|
|
467
|
+
deserializedTx = VersionedTransaction.deserialize(uint8Array)
|
|
468
|
+
} catch {
|
|
469
|
+
try {
|
|
470
|
+
deserializedTx = Transaction.from(uint8Array)
|
|
471
|
+
} catch {
|
|
472
|
+
throw new Error(
|
|
473
|
+
'Failed to deserialize transaction as either versioned or legacy format'
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
try {
|
|
478
|
+
const txHash = await baseAdapter.sendTransaction(
|
|
479
|
+
deserializedTx,
|
|
480
|
+
connection
|
|
481
|
+
)
|
|
482
|
+
return txHash
|
|
483
|
+
} catch {
|
|
484
|
+
throw new Error('Failed to send serialized transaction')
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
// @ts-expect-error return type includes VersionedTransaction
|
|
488
|
+
signSerializedTransaction: async (transaction: ArrayBufferLike) => {
|
|
489
|
+
const uint8Array = new Uint8Array(transaction)
|
|
490
|
+
|
|
491
|
+
let deserializedTx: Transaction | VersionedTransaction
|
|
492
|
+
try {
|
|
493
|
+
deserializedTx = VersionedTransaction.deserialize(uint8Array)
|
|
494
|
+
} catch {
|
|
495
|
+
try {
|
|
496
|
+
deserializedTx = Transaction.from(uint8Array)
|
|
497
|
+
} catch {
|
|
498
|
+
throw new Error(
|
|
499
|
+
'Failed to deserialize transaction as either versioned or legacy format'
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return await baseAdapter.signTransaction(deserializedTx)
|
|
505
|
+
},
|
|
506
|
+
// @ts-expect-error return type includes VersionedTransaction
|
|
507
|
+
signAllSerializedTransactions: async (
|
|
508
|
+
transactions: ArrayBufferLike[]
|
|
509
|
+
) => {
|
|
510
|
+
const deserializedTransactions: (
|
|
511
|
+
| Transaction
|
|
512
|
+
| VersionedTransaction
|
|
513
|
+
)[] = []
|
|
514
|
+
|
|
515
|
+
for (const transaction of transactions) {
|
|
516
|
+
const uint8Array = new Uint8Array(transaction)
|
|
517
|
+
|
|
518
|
+
let deserializedTx: Transaction | VersionedTransaction
|
|
519
|
+
try {
|
|
520
|
+
deserializedTx = VersionedTransaction.deserialize(uint8Array)
|
|
521
|
+
} catch {
|
|
522
|
+
try {
|
|
523
|
+
deserializedTx = Transaction.from(uint8Array)
|
|
524
|
+
} catch {
|
|
525
|
+
throw new Error(
|
|
526
|
+
'Failed to deserialize one or more transactions'
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
deserializedTransactions.push(deserializedTx)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return await baseAdapter.signAllTransactions(
|
|
535
|
+
deserializedTransactions
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
})
|
|
540
|
+
walletNames.add(baseAdapter.name)
|
|
541
|
+
}
|
|
542
|
+
} catch {
|
|
543
|
+
// Silently handle if Base Wallet adapter fails to initialize
|
|
544
|
+
}
|
|
545
|
+
|
|
378
546
|
return solanaWallets
|
|
379
547
|
}
|
|
380
548
|
|