Skip to content

Commit 559a62d

Browse files
committed
imp: improve connect flow with verified server signatures
1 parent 220a64e commit 559a62d

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
@@ -70,6 +70,7 @@
7070
"source-map-support": "^0.5.21",
7171
"three": "^0.173.0",
7272
"three-mesh-bvh": "^0.8.3",
73+
"tweetnacl": "^1.0.3",
7374
"yoga-layout": "^3.2.1"
7475
},
7576
"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
@@ -972,20 +972,6 @@ export class PlayerLocal extends Entity {
972972
})
973973
}
974974

975-
setWallet(wallet) {
976-
this.wallet = wallet
977-
this.data.wallet = {
978-
connecting: wallet.connecting,
979-
connected: wallet.connected,
980-
address: wallet.publicKey?.toString() || null,
981-
}
982-
this.world.events.emit('wallet', { playerId: this.data.id, wallet: this.data.wallet })
983-
this.world.network.send('entityModified', {
984-
id: this.data.id,
985-
wallet: this.data.wallet,
986-
})
987-
}
988-
989975
chat(msg) {
990976
this.nametag.active = false
991977
this.bubbleText.value = msg
@@ -1032,6 +1018,11 @@ export class PlayerLocal extends Entity {
10321018
this.data.roles = data.roles
10331019
changed = true
10341020
}
1021+
if (data.hasOwnProperty('wallet')) {
1022+
console.log('yo')
1023+
this.data.wallet = data.wallet
1024+
this.world.events.emit('wallet', { playerId: this.data.id, wallet: data.wallet })
1025+
}
10351026
if (avatarChanged) {
10361027
this.applyAvatar()
10371028
}

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
@@ -201,6 +201,14 @@ export class ClientNetwork extends System {
201201
this.world.emit('kick', code)
202202
}
203203

204+
onWalletConnect = () => {
205+
this.world.solana.connect()
206+
}
207+
208+
onWalletDisconnect = () => {
209+
this.world.solana.disconnect()
210+
}
211+
204212
onDepositRequest = data => {
205213
this.world.solana.onDepositRequest(data)
206214
}

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
@@ -281,11 +281,7 @@ export class ServerNetwork extends System {
281281
health: HEALTH_MAX,
282282
avatar: user.avatar || this.world.settings.avatar?.url || 'asset://avatar.vrm',
283283
roles: user.roles,
284-
wallet: {
285-
connecting: false,
286-
connected: false,
287-
address: null,
288-
},
284+
wallet: null,
289285
},
290286
true
291287
)
@@ -518,6 +514,14 @@ export class ServerNetwork extends System {
518514
socket.send('pong', time)
519515
}
520516

517+
onWalletConnect = (socket, data) => {
518+
this.world.solana.onWalletConnect(socket.id, data)
519+
}
520+
521+
onWalletDisconnect = socket => {
522+
this.world.solana.onWalletDisconnect(socket.id)
523+
}
524+
521525
onDepositResponse = (socket, data) => {
522526
this.world.solana.onDepositResponse(data)
523527
}

0 commit comments

Comments
 (0)