Skip to content

Commit 8286bc9

Browse files
committed
imp: improve connect flow with verified server signatures
1 parent 7a81277 commit 8286bc9

File tree

11 files changed

+127
-55
lines changed

11 files changed

+127
-55
lines changed

docs/solana.md

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,7 @@ You can generate a new wallet by running `node ./scripts/gen-wallet.js` and past
1212

1313
## player.wallet
1414

15-
Wallet information is automatically synchronized and can be accessed by apps on both the server and client.
16-
17-
**player.wallet.connecting**: A boolean that determines whether their wallet is in the process of connecting.
18-
19-
**player.wallet.connected**: A boolean that determines whether their wallet is connected.
20-
21-
**player.wallet.address**: The players wallet address, when connected.
15+
Either a `String` wallet address or `null`. This is automatically synchronized and accessable by apps on both the client and server.
2216

2317
## world.on('wallet', callback)
2418

@@ -27,23 +21,19 @@ Apps can also subscribe to global player wallet changes on both the client and s
2721
```jsx
2822
world.on('wallet', e => {
2923
// e.playerId
30-
// e.wallet.connecting
31-
// e.wallet.connected
32-
// e.wallet.address
24+
// e.wallet
3325
})
3426
```
3527

3628
## player.connect()
3729

38-
Client only.
30+
On the client this invokes a connect and sign flow, which is then verified on the server before updating the `player.wallet` value and emitting the `wallet` change event above.
3931

40-
Displays a modal for the player to select a wallet provider and then connect.
32+
On the server this sends a network event to the client to invoke the above.
4133

4234
## player.disconnect()
4335

44-
Client only.
45-
46-
Disconnects the players current wallet.
36+
Disconnects the players current wallet. Can be used on the client or server, and clears the `player.wallet` value. Also emits a `wallet` change event.
4737

4838
## player.deposit(amount)
4939

@@ -59,8 +49,4 @@ Server only.
5949

6050
Generates an unsigned transaction for the player to receive `amoutn` from the world wallet, sends it to the client to be signed, and is then signed and confirmed back on the server.
6151

62-
Returns a promise that resolves with the signature, or rejects with an error code.
63-
64-
65-
66-
52+
Returns a promise that resolves with the signature, or rejects with an error code.

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"source-map-support": "^0.5.21",
7474
"three": "^0.173.0",
7575
"three-mesh-bvh": "^0.8.3",
76+
"tweetnacl": "^1.0.3",
7677
"yoga-layout": "^3.2.1"
7778
},
7879
"devDependencies": {

src/client/components/Wallet.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ export function Logic({ world }) {
2727
const modal = useWalletModal()
2828
const { connecting, connected, publicKey } = wallet
2929
useEffect(() => {
30-
wallet.openModal = () => modal.setVisible(true)
31-
world.entities.player.setWallet(wallet)
30+
world.solana.bind({ wallet, modal })
3231
}, [connecting, connected, publicKey])
3332
return null
3433
}

src/core/entities/PlayerLocal.js

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -977,20 +977,6 @@ export class PlayerLocal extends Entity {
977977
})
978978
}
979979

980-
setWallet(wallet) {
981-
this.wallet = wallet
982-
this.data.wallet = {
983-
connecting: wallet.connecting,
984-
connected: wallet.connected,
985-
address: wallet.publicKey?.toString() || null,
986-
}
987-
this.world.events.emit('wallet', { playerId: this.data.id, wallet: this.data.wallet })
988-
this.world.network.send('entityModified', {
989-
id: this.data.id,
990-
wallet: this.data.wallet,
991-
})
992-
}
993-
994980
chat(msg) {
995981
this.nametag.active = false
996982
this.bubbleText.value = msg
@@ -1037,6 +1023,11 @@ export class PlayerLocal extends Entity {
10371023
this.data.roles = data.roles
10381024
changed = true
10391025
}
1026+
if (data.hasOwnProperty('wallet')) {
1027+
console.log('yo')
1028+
this.data.wallet = data.wallet
1029+
this.world.events.emit('wallet', { playerId: this.data.id, wallet: data.wallet })
1030+
}
10401031
if (avatarChanged) {
10411032
this.applyAvatar()
10421033
}

src/core/extras/createPlayerProxy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,10 @@ export function createPlayerProxy(entity, player) {
168168
return player.data.wallet
169169
},
170170
connect() {
171-
return world.solana.connect()
171+
return world.solana.connect(player)
172172
},
173173
disconnect() {
174-
return world.solana.disconnect()
174+
return world.solana.disconnect(player)
175175
},
176176
deposit(amount) {
177177
return world.solana.deposit(entity, player, amount)

src/core/packets.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const names = [
2222
'kick',
2323
'ping',
2424
'pong',
25+
'walletConnect',
26+
'walletDisconnect',
2527
'depositRequest',
2628
'depositResponse',
2729
'withdrawRequest',

src/core/systems/ClientNetwork.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,14 @@ export class ClientNetwork extends System {
205205
this.world.emit('kick', code)
206206
}
207207

208+
onWalletConnect = () => {
209+
this.world.solana.connect()
210+
}
211+
212+
onWalletDisconnect = () => {
213+
this.world.solana.disconnect()
214+
}
215+
208216
onDepositRequest = data => {
209217
this.world.solana.onDepositRequest(data)
210218
}

src/core/systems/ClientSolana.js

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,60 @@
11
import { Transaction } from '@solana/web3.js'
22
import { System } from './System'
3+
import { storage } from '../storage'
4+
import bs58 from 'bs58'
5+
6+
const key = 'hyp:solana:auths'
7+
const template = 'Connect to world:\n{address}'
38

49
export class ClientSolana extends System {
510
constructor(world) {
611
super(world)
12+
this.wallet = null
13+
this.modal = null
14+
this.auths = storage.get(key, []) // [...{ address, signature }]
15+
this.connected = false
716
}
817

9-
connect() {
10-
const player = this.world.entities.player
11-
if (player.wallet.connected) return console.warn('[solana] already connected')
12-
player.wallet.openModal()
18+
async bind({ wallet, modal }) {
19+
this.wallet = wallet
20+
this.modal = modal
21+
if (this.wallet.connected && !this.connected) {
22+
const address = this.wallet.publicKey.toString()
23+
let auth = this.auths.find(auth => auth.address === address)
24+
if (!auth) {
25+
const text = template.replace('{address}', address)
26+
const message = new TextEncoder().encode(text)
27+
const signature_ = await this.wallet.signMessage(message)
28+
const signature = bs58.encode(signature_)
29+
auth = { address, signature }
30+
this.auths.push(auth)
31+
storage.set(key, this.auths)
32+
}
33+
this.connected = true
34+
this.world.network.send('walletConnect', auth)
35+
}
36+
if (!this.wallet.connected && this.connected) {
37+
this.connected = false
38+
this.world.network.send('walletDisconnect')
39+
}
1340
}
1441

15-
disconnect() {
16-
const player = this.world.entities.player
17-
if (!player.wallet.connected) return console.warn('[solana] already disconnected')
18-
player.wallet.disconnect()
42+
connect(player) {
43+
if (player && player.data.id !== this.world.network.id) {
44+
throw new Error('[solana] cannot connect a remote player from client')
45+
}
46+
if (!this.wallet) return
47+
if (this.wallet.connected) return
48+
this.modal.setVisible(true)
49+
}
50+
51+
disconnect(player) {
52+
if (player && player.data.id !== this.world.network.id) {
53+
throw new Error('[solana] cannot disconnect a remote player from client')
54+
}
55+
if (!this.wallet) return
56+
if (!this.wallet.connected) return
57+
this.wallet.disconnect()
1958
}
2059

2160
deposit(playerId, amount) {

src/core/systems/ServerNetwork.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,7 @@ export class ServerNetwork extends System {
287287
avatar: user.avatar || this.world.settings.avatar?.url || 'asset://avatar.vrm',
288288
sessionAvatar: avatar || null,
289289
roles: user.roles,
290-
wallet: {
291-
connecting: false,
292-
connected: false,
293-
address: null,
294-
},
290+
wallet: null,
295291
},
296292
true
297293
)
@@ -525,6 +521,14 @@ export class ServerNetwork extends System {
525521
socket.send('pong', time)
526522
}
527523

524+
onWalletConnect = (socket, data) => {
525+
this.world.solana.onWalletConnect(socket.id, data)
526+
}
527+
528+
onWalletDisconnect = socket => {
529+
this.world.solana.onWalletDisconnect(socket.id)
530+
}
531+
528532
onDepositResponse = (socket, data) => {
529533
this.world.solana.onDepositResponse(data)
530534
}

0 commit comments

Comments
 (0)