@meddleware/access-gate-ui 0.1.0 → 0.1.3
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/package.json +19 -7
- package/src/App.vue +23 -129
- package/src/components/AccessGateView.vue +129 -0
- package/src/components/GateCard.vue +19 -0
- package/src/config.ts +4 -4
- package/src/index.ts +10 -0
- package/src/wallet.ts +30 -123
package/package.json
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/access-gate-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Operator console for creating and managing on-chain Sui access gates — create gates, configure settings, airdrop NFTs, and freeze gates via a wallet-connected SPA.",
|
|
5
|
-
"author": "
|
|
5
|
+
"author": "Meddleware <dev@meddleware.co.uk>",
|
|
6
6
|
"license": "0BSD",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/meddleware-org/access-gate-ui.git"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
|
+
"sideEffects": [
|
|
13
|
+
"*.css",
|
|
14
|
+
"*.vue"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./src/index.ts",
|
|
19
|
+
"default": "./src/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
12
23
|
"files": [
|
|
13
24
|
"src",
|
|
14
25
|
"index.html",
|
|
@@ -26,11 +37,12 @@
|
|
|
26
37
|
"access": "public"
|
|
27
38
|
},
|
|
28
39
|
"dependencies": {
|
|
29
|
-
"@meddleware/design-tokens": "^0.1.
|
|
30
|
-
"@meddleware/ui": "^0.1.
|
|
31
|
-
"@meddleware/nft-gate-client": "^0.0.
|
|
32
|
-
"@
|
|
33
|
-
"@mysten/
|
|
40
|
+
"@meddleware/design-tokens": "^0.1.2",
|
|
41
|
+
"@meddleware/ui": "^0.1.6",
|
|
42
|
+
"@meddleware/nft-gate-client": "^0.0.6",
|
|
43
|
+
"@meddleware/wallet-adapter": "^0.0.2",
|
|
44
|
+
"@mysten/sui": "^2.28.0",
|
|
45
|
+
"@mysten/wallet-standard": "^0.20.0",
|
|
34
46
|
"vue": "^3.5.40"
|
|
35
47
|
},
|
|
36
48
|
"devDependencies": {
|
package/src/App.vue
CHANGED
|
@@ -1,160 +1,54 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import { NETWORK } from './config.js'
|
|
6
|
-
import { PACKAGE_ID, listMyGates } from './gates.js'
|
|
2
|
+
// Standalone shell for the Access Gate SPA: app header + footer wrapping the core tool view.
|
|
3
|
+
// The core UI lives in AccessGateView.vue (also exported for inline embedding in the dashboard).
|
|
4
|
+
import { AppHeader, AppFooter, ColorModeControl, UiButton, useColorMode } from '@meddleware/ui'
|
|
7
5
|
import { useWallet } from './wallet.js'
|
|
8
|
-
import
|
|
9
|
-
import GateList from './components/GateList.vue'
|
|
10
|
-
|
|
11
|
-
type Tab = 'create' | 'gates'
|
|
12
|
-
const activeTab = ref<Tab>('gates')
|
|
6
|
+
import AccessGateView from './components/AccessGateView.vue'
|
|
13
7
|
|
|
8
|
+
const { mode, set } = useColorMode('dark')
|
|
14
9
|
const { wallets, account, connect, disconnect } = useWallet()
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
const loadingGates = ref(false)
|
|
18
|
-
const loadError = ref<string | null>(null)
|
|
19
|
-
|
|
20
|
-
const deployed = PACKAGE_ID !== ''
|
|
21
|
-
|
|
22
|
-
async function reloadGates(): Promise<void> {
|
|
23
|
-
if (!account.value || !deployed) return
|
|
24
|
-
loadingGates.value = true
|
|
25
|
-
loadError.value = null
|
|
26
|
-
try {
|
|
27
|
-
gates.value = await listMyGates(account.value.address)
|
|
28
|
-
} catch (e) {
|
|
29
|
-
loadError.value = e instanceof Error ? e.message : String(e)
|
|
30
|
-
} finally {
|
|
31
|
-
loadingGates.value = false
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function onConnectFirst(): Promise<void> {
|
|
11
|
+
async function onConnect(): Promise<void> {
|
|
36
12
|
const w = wallets.value[0]
|
|
37
|
-
if (
|
|
38
|
-
await connect(w)
|
|
39
|
-
await reloadGates()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function onCreated(): void {
|
|
43
|
-
activeTab.value = 'gates'
|
|
44
|
-
void reloadGates()
|
|
13
|
+
if (w) await connect(w)
|
|
45
14
|
}
|
|
46
15
|
</script>
|
|
47
16
|
|
|
48
17
|
<template>
|
|
49
|
-
<div class="
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<div class="wallet">
|
|
18
|
+
<div class="app">
|
|
19
|
+
<AppHeader variant="dark">
|
|
20
|
+
<template #brand>
|
|
21
|
+
<span>Access Gate</span>
|
|
22
|
+
</template>
|
|
23
|
+
<template #actions>
|
|
56
24
|
<template v-if="account">
|
|
57
25
|
<span class="addr">{{ account.address.slice(0, 8) }}…{{ account.address.slice(-4) }}</span>
|
|
58
26
|
<UiButton variant="ghost" @click="disconnect">Disconnect</UiButton>
|
|
59
27
|
</template>
|
|
60
28
|
<template v-else>
|
|
61
|
-
<UiButton :disabled="!wallets.length" @click="
|
|
29
|
+
<UiButton :disabled="!wallets.length" @click="onConnect">
|
|
62
30
|
{{ wallets.length ? 'Connect wallet' : 'No wallet detected' }}
|
|
63
31
|
</UiButton>
|
|
64
32
|
</template>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<UiNotice v-if="!deployed" type="error">
|
|
69
|
-
The access_gate contract is not deployed on {{ NETWORK }}. Switch to a supported network.
|
|
70
|
-
</UiNotice>
|
|
71
|
-
|
|
72
|
-
<template v-else-if="account">
|
|
73
|
-
<nav class="tabs" aria-label="Sections">
|
|
74
|
-
<button type="button" class="tab" :class="{ active: activeTab === 'gates' }" @click="activeTab = 'gates'; reloadGates()">
|
|
75
|
-
My gates
|
|
76
|
-
</button>
|
|
77
|
-
<button type="button" class="tab" :class="{ active: activeTab === 'create' }" @click="activeTab = 'create'">
|
|
78
|
-
Create gate
|
|
79
|
-
</button>
|
|
80
|
-
</nav>
|
|
81
|
-
|
|
82
|
-
<UiNotice v-if="loadError" type="error">{{ loadError }}</UiNotice>
|
|
83
|
-
|
|
84
|
-
<section v-if="activeTab === 'gates'">
|
|
85
|
-
<GateList :gates="gates" :loading="loadingGates" @changed="reloadGates" />
|
|
86
|
-
</section>
|
|
87
|
-
<section v-else>
|
|
88
|
-
<CreateGateForm :address="account.address" @created="onCreated" />
|
|
89
|
-
</section>
|
|
90
|
-
</template>
|
|
33
|
+
<ColorModeControl :model-value="mode" @update:model-value="set" />
|
|
34
|
+
</template>
|
|
35
|
+
</AppHeader>
|
|
91
36
|
|
|
92
|
-
<
|
|
93
|
-
Connect a Sui wallet to create and manage your access gates.
|
|
94
|
-
</UiNotice>
|
|
37
|
+
<AccessGateView />
|
|
95
38
|
|
|
96
|
-
<
|
|
97
|
-
<p>© MeddleWare · <a href="https://sui.meddleware.co.uk">more SUI tools</a></p>
|
|
98
|
-
</footer>
|
|
39
|
+
<AppFooter />
|
|
99
40
|
</div>
|
|
100
41
|
</template>
|
|
101
42
|
|
|
102
43
|
<style scoped>
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
margin: 0 auto;
|
|
106
|
-
padding: 2rem 1.25rem 4rem;
|
|
107
|
-
}
|
|
108
|
-
.head {
|
|
109
|
-
display: flex;
|
|
110
|
-
justify-content: space-between;
|
|
111
|
-
align-items: flex-start;
|
|
112
|
-
gap: 1rem;
|
|
113
|
-
flex-wrap: wrap;
|
|
114
|
-
}
|
|
115
|
-
.head h1 {
|
|
116
|
-
margin: 0;
|
|
117
|
-
font-size: 1.8rem;
|
|
118
|
-
color: var(--text);
|
|
119
|
-
}
|
|
120
|
-
.sub {
|
|
121
|
-
color: var(--muted);
|
|
122
|
-
margin: 0.25rem 0 0;
|
|
123
|
-
}
|
|
124
|
-
.wallet {
|
|
44
|
+
.app {
|
|
45
|
+
min-height: 100vh;
|
|
125
46
|
display: flex;
|
|
126
|
-
|
|
127
|
-
gap: 0.75rem;
|
|
47
|
+
flex-direction: column;
|
|
128
48
|
}
|
|
49
|
+
|
|
129
50
|
.addr {
|
|
130
51
|
font-family: monospace;
|
|
131
52
|
font-size: 0.9rem;
|
|
132
|
-
color: var(--text);
|
|
133
|
-
}
|
|
134
|
-
.tabs {
|
|
135
|
-
display: flex;
|
|
136
|
-
gap: 0.25rem;
|
|
137
|
-
margin: 1.25rem 0 1rem;
|
|
138
|
-
border-bottom: 2px solid var(--border);
|
|
139
|
-
}
|
|
140
|
-
.tab {
|
|
141
|
-
background: none;
|
|
142
|
-
border: none;
|
|
143
|
-
border-bottom: 2px solid transparent;
|
|
144
|
-
padding: 0.5rem 1rem;
|
|
145
|
-
margin-bottom: -2px;
|
|
146
|
-
cursor: pointer;
|
|
147
|
-
font-size: 0.95rem;
|
|
148
|
-
color: var(--muted);
|
|
149
|
-
}
|
|
150
|
-
.tab.active {
|
|
151
|
-
border-bottom-color: var(--mw-accent-500, var(--text));
|
|
152
|
-
color: var(--text);
|
|
153
|
-
font-weight: 600;
|
|
154
|
-
}
|
|
155
|
-
.foot {
|
|
156
|
-
margin-top: 3rem;
|
|
157
|
-
font-size: 0.85rem;
|
|
158
|
-
color: var(--muted);
|
|
159
53
|
}
|
|
160
54
|
</style>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Core Access Gate tool UI (list + create + manage gates), free of any app shell.
|
|
3
|
+
// Rendered standalone by access-gate-ui's App.vue and inline by the dashboard. Wallet state
|
|
4
|
+
// comes from the shared @meddleware/wallet-adapter singleton (via ./wallet.js): connecting here
|
|
5
|
+
// or in any other inline tool view (or the dashboard header) reflects everywhere.
|
|
6
|
+
//
|
|
7
|
+
// Gate loading is driven by watching the connected account, so it works whether the connection
|
|
8
|
+
// is made from this app's own header (standalone) or the dashboard's shared control (embedded).
|
|
9
|
+
import { ref, watch } from 'vue'
|
|
10
|
+
import { UiNotice } from '@meddleware/ui'
|
|
11
|
+
import type { OwnedGate } from '@meddleware/nft-gate-client'
|
|
12
|
+
import { NETWORK } from '../config.js'
|
|
13
|
+
import { PACKAGE_ID, listMyGates } from '../gates.js'
|
|
14
|
+
import { useWallet } from '../wallet.js'
|
|
15
|
+
import CreateGateForm from './CreateGateForm.vue'
|
|
16
|
+
import GateList from './GateList.vue'
|
|
17
|
+
|
|
18
|
+
type Tab = 'create' | 'gates'
|
|
19
|
+
const activeTab = ref<Tab>('gates')
|
|
20
|
+
|
|
21
|
+
const { account } = useWallet()
|
|
22
|
+
|
|
23
|
+
const gates = ref<OwnedGate[]>([])
|
|
24
|
+
const loadingGates = ref(false)
|
|
25
|
+
const loadError = ref<string | null>(null)
|
|
26
|
+
|
|
27
|
+
const deployed = PACKAGE_ID !== ''
|
|
28
|
+
|
|
29
|
+
async function reloadGates(): Promise<void> {
|
|
30
|
+
if (!account.value || !deployed) return
|
|
31
|
+
loadingGates.value = true
|
|
32
|
+
loadError.value = null
|
|
33
|
+
try {
|
|
34
|
+
gates.value = await listMyGates(account.value.address)
|
|
35
|
+
} catch (e) {
|
|
36
|
+
loadError.value = e instanceof Error ? e.message : String(e)
|
|
37
|
+
} finally {
|
|
38
|
+
loadingGates.value = false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Load (and clear) gates as the shared wallet connects/disconnects — independent of where the
|
|
43
|
+
// connect action originates.
|
|
44
|
+
watch(
|
|
45
|
+
() => account.value?.address ?? null,
|
|
46
|
+
(addr) => {
|
|
47
|
+
if (addr) void reloadGates()
|
|
48
|
+
else gates.value = []
|
|
49
|
+
},
|
|
50
|
+
{ immediate: true },
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
function onCreated(): void {
|
|
54
|
+
activeTab.value = 'gates'
|
|
55
|
+
void reloadGates()
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<div class="page">
|
|
61
|
+
<p class="sub">Create and manage on-chain access gates on Sui ({{ NETWORK }}).</p>
|
|
62
|
+
|
|
63
|
+
<UiNotice v-if="!deployed" type="error">
|
|
64
|
+
The access_gate contract is not deployed on {{ NETWORK }}. Switch to a supported network.
|
|
65
|
+
</UiNotice>
|
|
66
|
+
|
|
67
|
+
<template v-else-if="account">
|
|
68
|
+
<nav class="tabs" aria-label="Sections">
|
|
69
|
+
<button type="button" class="tab" :class="{ active: activeTab === 'gates' }" @click="activeTab = 'gates'; reloadGates()">
|
|
70
|
+
My gates
|
|
71
|
+
</button>
|
|
72
|
+
<button type="button" class="tab" :class="{ active: activeTab === 'create' }" @click="activeTab = 'create'">
|
|
73
|
+
Create gate
|
|
74
|
+
</button>
|
|
75
|
+
</nav>
|
|
76
|
+
|
|
77
|
+
<UiNotice v-if="loadError" type="error">{{ loadError }}</UiNotice>
|
|
78
|
+
|
|
79
|
+
<section v-if="activeTab === 'gates'">
|
|
80
|
+
<GateList :gates="gates" :loading="loadingGates" @changed="reloadGates" />
|
|
81
|
+
</section>
|
|
82
|
+
<section v-else>
|
|
83
|
+
<CreateGateForm :address="account.address" @created="onCreated" />
|
|
84
|
+
</section>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<UiNotice v-else type="info">
|
|
88
|
+
Connect a Sui wallet to create and manage your access gates.
|
|
89
|
+
</UiNotice>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<style scoped>
|
|
94
|
+
.page {
|
|
95
|
+
max-width: 720px;
|
|
96
|
+
margin: 0 auto;
|
|
97
|
+
padding: 2rem 1.25rem 4rem;
|
|
98
|
+
flex: 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.sub {
|
|
102
|
+
color: var(--muted);
|
|
103
|
+
margin: 0.25rem 0 1.25rem;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.tabs {
|
|
107
|
+
display: flex;
|
|
108
|
+
gap: 0.25rem;
|
|
109
|
+
margin: 0 0 1rem;
|
|
110
|
+
border-bottom: 2px solid var(--border);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.tab {
|
|
114
|
+
background: none;
|
|
115
|
+
border: none;
|
|
116
|
+
border-bottom: 2px solid transparent;
|
|
117
|
+
padding: 0.5rem 1rem;
|
|
118
|
+
margin-bottom: -2px;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
font-size: 0.95rem;
|
|
121
|
+
color: var(--muted);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.tab.active {
|
|
125
|
+
border-bottom-color: var(--accent);
|
|
126
|
+
color: var(--text);
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
}
|
|
129
|
+
</style>
|
|
@@ -43,6 +43,13 @@ function priceLabel(mist: bigint): string {
|
|
|
43
43
|
|
|
44
44
|
<div class="actions">
|
|
45
45
|
<UiButton variant="ghost" @click="open = !open">{{ open ? 'Hide' : 'Manage' }}</UiButton>
|
|
46
|
+
<a
|
|
47
|
+
class="seal-link"
|
|
48
|
+
:href="`https://sui-seal.meddleware.co.uk/?gate=${gate.gateId}`"
|
|
49
|
+
target="_blank"
|
|
50
|
+
rel="noopener"
|
|
51
|
+
title="Seal-encrypt content that only this gate's pass-holders can unlock"
|
|
52
|
+
>🔒 Add unlockable content →</a>
|
|
46
53
|
</div>
|
|
47
54
|
|
|
48
55
|
<div v-if="open" class="manage">
|
|
@@ -97,6 +104,18 @@ function priceLabel(mist: bigint): string {
|
|
|
97
104
|
}
|
|
98
105
|
.actions {
|
|
99
106
|
margin-top: 0.75rem;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 1rem;
|
|
110
|
+
flex-wrap: wrap;
|
|
111
|
+
}
|
|
112
|
+
.seal-link {
|
|
113
|
+
color: var(--accent, #c0503f);
|
|
114
|
+
text-decoration: none;
|
|
115
|
+
font-size: 0.85rem;
|
|
116
|
+
}
|
|
117
|
+
.seal-link:hover {
|
|
118
|
+
text-decoration: underline;
|
|
100
119
|
}
|
|
101
120
|
.manage {
|
|
102
121
|
margin-top: 0.75rem;
|
package/src/config.ts
CHANGED
|
@@ -11,11 +11,11 @@ const env = (import.meta as unknown as { env?: Record<string, string | undefined
|
|
|
11
11
|
export const NETWORK: SuiNetwork = (env.VITE_NETWORK as SuiNetwork) || 'testnet'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* gRPC-web endpoint used to build + execute gate transactions and read gate state. The Sui SDK's
|
|
15
|
+
* JSON-RPC client is deprecated, so this must be a gRPC-web-capable endpoint (the Mysten public
|
|
16
|
+
* fullnodes serve gRPC-web at :443 via the browser Fetch transport).
|
|
17
17
|
*/
|
|
18
18
|
export const RPC_URLS: Record<SuiNetwork, string> = {
|
|
19
|
-
testnet: env.VITE_RPC_TESTNET || 'https://
|
|
19
|
+
testnet: env.VITE_RPC_TESTNET || 'https://fullnode.testnet.sui.io:443',
|
|
20
20
|
mainnet: env.VITE_RPC_MAINNET || 'https://fullnode.mainnet.sui.io:443',
|
|
21
21
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @meddleware/access-gate-ui — library entry.
|
|
2
|
+
//
|
|
3
|
+
// Exports the core Access Gate tool view (no app shell) for inline embedding in the dashboard.
|
|
4
|
+
// The standalone SPA (App.vue + main.ts) is unaffected and still builds/deploys as before.
|
|
5
|
+
//
|
|
6
|
+
// Consumers must import the shared design tokens + UI base once at their entry:
|
|
7
|
+
// import '@meddleware/design-tokens/tokens.css'
|
|
8
|
+
// import '@meddleware/ui/base.css'
|
|
9
|
+
|
|
10
|
+
export { default as AccessGateView } from './components/AccessGateView.vue'
|
package/src/wallet.ts
CHANGED
|
@@ -1,138 +1,45 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
|
|
1
|
+
// Thin access-gate-ui shim over the shared @meddleware/wallet-adapter singleton.
|
|
2
|
+
//
|
|
3
|
+
// The adapter is network-agnostic (RPC URL passed per call); this shim binds access-gate-ui's
|
|
4
|
+
// RPC_URLS so call sites keep the one-arg ergonomics (`getSuiClient(NETWORK)` /
|
|
5
|
+
// `buildExecutor(NETWORK)`). Because the adapter is a module singleton, the wallet connection is
|
|
6
|
+
// shared with any other tool view rendered in the same window (e.g. the dashboard).
|
|
7
|
+
import {
|
|
8
|
+
useWallet as useWalletBase,
|
|
9
|
+
getSuiClient as getSuiClientBase,
|
|
10
|
+
buildExecutor as buildExecutorBase,
|
|
11
|
+
} from '@meddleware/wallet-adapter'
|
|
12
|
+
import type { Executor } from '@meddleware/wallet-adapter'
|
|
9
13
|
import type { SuiNetwork } from './config.js'
|
|
10
14
|
import { RPC_URLS } from './config.js'
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
export type { Executor }
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const connecting = ref(false)
|
|
18
|
-
const error = ref<string | null>(null)
|
|
19
|
-
|
|
20
|
-
const clients = new Map<SuiNetwork, SuiJsonRpcClient>()
|
|
21
|
-
/** Return a memoised {@link SuiJsonRpcClient} for the network (one instance per network). */
|
|
22
|
-
export function getSuiClient(network: SuiNetwork): SuiJsonRpcClient {
|
|
23
|
-
let c = clients.get(network)
|
|
24
|
-
if (!c) {
|
|
25
|
-
c = new SuiJsonRpcClient({ url: RPC_URLS[network], network })
|
|
26
|
-
clients.set(network, c)
|
|
27
|
-
}
|
|
28
|
-
return c
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function refreshWallets(): void {
|
|
32
|
-
// markRaw: extension Wallet objects expose name/icon as ES-private-field getters
|
|
33
|
-
// that throw when accessed through a Vue reactive Proxy.
|
|
34
|
-
wallets.value = getWallets()
|
|
35
|
-
.get()
|
|
36
|
-
.filter((w) => isWalletWithRequiredFeatureSet(w, [...REQUIRED_FEATURES]))
|
|
37
|
-
.map((w) => markRaw(w))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let initialised = false
|
|
41
|
-
function init(): void {
|
|
42
|
-
if (initialised) return
|
|
43
|
-
initialised = true
|
|
44
|
-
const api = getWallets()
|
|
45
|
-
refreshWallets()
|
|
46
|
-
api.on('register', refreshWallets)
|
|
47
|
-
api.on('unregister', refreshWallets)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function connect(wallet: Wallet): Promise<void> {
|
|
51
|
-
error.value = null
|
|
52
|
-
connecting.value = true
|
|
53
|
-
try {
|
|
54
|
-
const feature = wallet.features['standard:connect'] as {
|
|
55
|
-
connect: () => Promise<{ accounts: readonly WalletAccount[] }>
|
|
56
|
-
}
|
|
57
|
-
const { accounts } = await feature.connect()
|
|
58
|
-
if (!accounts.length) throw new Error('Wallet returned no accounts.')
|
|
59
|
-
currentWallet.value = markRaw(wallet)
|
|
60
|
-
account.value = markRaw(accounts[0])
|
|
61
|
-
} catch (e) {
|
|
62
|
-
error.value = e instanceof Error ? e.message : String(e)
|
|
63
|
-
throw e
|
|
64
|
-
} finally {
|
|
65
|
-
connecting.value = false
|
|
66
|
-
}
|
|
18
|
+
/** Memoised Sui JSON-RPC client for the network, using access-gate-ui's configured RPC URL. */
|
|
19
|
+
export function getSuiClient(network: SuiNetwork) {
|
|
20
|
+
return getSuiClientBase(network, RPC_URLS[network])
|
|
67
21
|
}
|
|
68
22
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
| undefined
|
|
73
|
-
void disc?.disconnect?.()
|
|
74
|
-
currentWallet.value = null
|
|
75
|
-
account.value = null
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Transaction executor bound to the connected wallet: sign+execute a PTB and await finality. */
|
|
79
|
-
export interface Executor {
|
|
80
|
-
address: string
|
|
81
|
-
signAndExecute(tx: Transaction): Promise<{ digest: string }>
|
|
82
|
-
waitForTransaction(digest: string): Promise<unknown>
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Build an executor bound to the connected wallet + network. */
|
|
86
|
-
export async function buildExecutor(network: SuiNetwork): Promise<Executor> {
|
|
87
|
-
const wallet = currentWallet.value
|
|
88
|
-
const acct = account.value
|
|
89
|
-
if (!wallet || !acct) throw new Error('Connect a wallet first.')
|
|
90
|
-
const client = getSuiClient(network)
|
|
91
|
-
const chain = `sui:${network}` as const
|
|
92
|
-
|
|
93
|
-
const signFeature = wallet.features['sui:signTransaction'] as {
|
|
94
|
-
signTransaction: (input: {
|
|
95
|
-
transaction: Transaction
|
|
96
|
-
account: WalletAccount
|
|
97
|
-
chain: `sui:${string}`
|
|
98
|
-
}) => Promise<{ bytes: string; signature: string }>
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
address: acct.address,
|
|
103
|
-
async signAndExecute(tx: Transaction): Promise<{ digest: string }> {
|
|
104
|
-
const { bytes, signature } = await signFeature.signTransaction({
|
|
105
|
-
transaction: tx,
|
|
106
|
-
account: acct,
|
|
107
|
-
chain,
|
|
108
|
-
})
|
|
109
|
-
const res = await client.executeTransactionBlock({
|
|
110
|
-
transactionBlock: bytes,
|
|
111
|
-
signature,
|
|
112
|
-
options: { showEffects: true },
|
|
113
|
-
})
|
|
114
|
-
return { digest: res.digest }
|
|
115
|
-
},
|
|
116
|
-
async waitForTransaction(digest: string): Promise<unknown> {
|
|
117
|
-
return client.waitForTransaction({ digest })
|
|
118
|
-
},
|
|
119
|
-
}
|
|
23
|
+
/** Build a transaction executor bound to the connected wallet + access-gate-ui's RPC URL. */
|
|
24
|
+
export function buildExecutor(network: SuiNetwork): Promise<Executor> {
|
|
25
|
+
return buildExecutorBase(network, RPC_URLS[network])
|
|
120
26
|
}
|
|
121
27
|
|
|
122
28
|
/**
|
|
123
|
-
* Wallet composable
|
|
124
|
-
*
|
|
29
|
+
* Wallet composable bound to access-gate-ui's network config. Delegates to the shared adapter
|
|
30
|
+
* singleton; gate management needs `sui:signTransaction` so it's requested for discovery.
|
|
125
31
|
*/
|
|
126
32
|
export function useWallet() {
|
|
127
|
-
|
|
33
|
+
const base = useWalletBase({ requiredFeatures: ['sui:signTransaction'] })
|
|
128
34
|
return {
|
|
129
|
-
wallets:
|
|
130
|
-
currentWallet:
|
|
131
|
-
account:
|
|
132
|
-
connecting:
|
|
133
|
-
error:
|
|
134
|
-
connect,
|
|
135
|
-
disconnect,
|
|
35
|
+
wallets: base.wallets,
|
|
36
|
+
currentWallet: base.currentWallet,
|
|
37
|
+
account: base.account,
|
|
38
|
+
connecting: base.connecting,
|
|
39
|
+
error: base.error,
|
|
40
|
+
connect: base.connect,
|
|
41
|
+
disconnect: base.disconnect,
|
|
42
|
+
getSuiClient,
|
|
136
43
|
buildExecutor,
|
|
137
44
|
}
|
|
138
45
|
}
|