@meddleware/dev 0.0.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.
@@ -0,0 +1,101 @@
1
+ # Environment setup
2
+
3
+ ## Active network
4
+
5
+ The Sui CLI stores network configuration in `~/.sui/sui_config/client.yaml`. To switch networks:
6
+
7
+ ```bash
8
+ sui client switch --env testnet
9
+ sui client envs # list all configured environments
10
+ ```
11
+
12
+ ## Active address
13
+
14
+ ```bash
15
+ sui client active-address # show the current active address
16
+ sui client addresses # list all known addresses
17
+ sui client switch --address <ADDRESS>
18
+ ```
19
+
20
+ ## Gas and the testnet faucet
21
+
22
+ Fund a testnet address from the faucet:
23
+
24
+ ```bash
25
+ # Via CLI
26
+ sui client faucet --address <ADDRESS>
27
+
28
+ # Or navigate to https://faucet.sui.io/ and paste the address
29
+ ```
30
+
31
+ Check balance:
32
+
33
+ ```bash
34
+ sui client balance
35
+ ```
36
+
37
+ ## Localnet
38
+
39
+ For contract development and integration testing, a local network is the fastest iteration loop:
40
+
41
+ ```bash
42
+ # Start a local network with a built-in faucet
43
+ sui start --with-faucet
44
+ # → Fullnode: http://127.0.0.1:9000
45
+ # → Faucet: http://127.0.0.1:9123
46
+
47
+ # Add the localnet environment (first time only)
48
+ sui client new-env --alias localnet --rpc http://127.0.0.1:9000
49
+ sui client switch --env localnet
50
+
51
+ # Fund the active address from the local faucet
52
+ sui client faucet
53
+ ```
54
+
55
+ ### Publishing contracts to localnet
56
+
57
+ ```bash
58
+ cd blockchain/sui/contracts/core
59
+ sui move build --build-env localnet
60
+ sui client publish --gas-budget 200000000
61
+ ```
62
+
63
+ Keep the published package address — you'll need it to call entry functions in tests.
64
+
65
+ ## TypeScript SDK — network configuration
66
+
67
+ The `@mysten/sui` SDK reads the network from your build config or at runtime:
68
+
69
+ ```ts
70
+ import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'
71
+
72
+ const network = (import.meta.env.VITE_NETWORK ?? 'testnet') as 'localnet' | 'testnet' | 'mainnet'
73
+
74
+ const client = new SuiClient({
75
+ url: network === 'localnet'
76
+ ? 'http://127.0.0.1:9000'
77
+ : getFullnodeUrl(network),
78
+ })
79
+ ```
80
+
81
+ ::: warning Public fullnodes
82
+ Public testnet fullnodes use **gRPC/GraphQL** (JSON-RPC was retired in September 2026). The `SuiClient` from `@mysten/sui ^2.x` uses GraphQL transport by default — ensure you are on a current SDK version.
83
+ :::
84
+
85
+ ## Useful CLI commands
86
+
87
+ ```bash
88
+ # Object details
89
+ sui client object <OBJECT_ID>
90
+
91
+ # Call a Move function
92
+ sui client call \
93
+ --package <PACKAGE_ID> \
94
+ --module <MODULE> \
95
+ --function <FUNCTION> \
96
+ --args <ARG1> <ARG2> \
97
+ --gas-budget 10000000
98
+
99
+ # Read Move events
100
+ sui client events --package <PACKAGE_ID>
101
+ ```
@@ -0,0 +1,46 @@
1
+ # Sui development
2
+
3
+ Guides for building on the Meddleware Sui contracts and SDKs.
4
+
5
+ ## In this section
6
+
7
+ | Topic | Description |
8
+ | --- | --- |
9
+ | [Environment setup](./environment) | Localnet, testnet, active address, gas |
10
+ | [PTB patterns](./ptb-patterns) | TypeScript transaction building patterns |
11
+ | [Walrus Storage](./walrus-storage/) | SDK setup and integration guide |
12
+ | [Sealed Storage](./sealed-storage/) | Encrypt/store/decrypt with on-chain policies |
13
+ | [Access Gate](./access-gate/) | NFT gate passes and gateway deployment |
14
+ | [DAO](./dao/) | Governance interaction patterns |
15
+
16
+ ## Contract layout
17
+
18
+ The on-chain packages are published under:
19
+
20
+ ```
21
+ blockchain/sui/contracts/
22
+ ├── core/ — vault_core (accounting, share minting/burning)
23
+ ├── adapters/ — vault_adapters (strategy integration)
24
+ ├── governor/ — vault_governor (DaoAdminCap-gated ops)
25
+ ├── fee_distributor/— vault_fee_distributor
26
+ ├── config/ — vault_config (DAO-governed parameters)
27
+ ├── dao/ — vault_dao
28
+ └── access-gate/ — access_gate (NFT pass minting/consumption)
29
+ ```
30
+
31
+ ## SDK packages
32
+
33
+ | Package | npm | Purpose |
34
+ | --- | --- | --- |
35
+ | `@meddleware/walrus-client` | ✓ | Walrus blob upload, read, extend, enumerate |
36
+ | `@meddleware/seal-client` | ✓ | Encrypt/store/decrypt with Seal + Walrus |
37
+ | `@meddleware/nft-gate-client` | ✓ | Access Gate challenge/proof construction |
38
+ | `@meddleware/ui` | ✓ | Vue 3 components (includes `suiExplorerUrl`, `CopyableAddress`) |
39
+
40
+ ## Testnet vs mainnet
41
+
42
+ All contract addresses in this documentation are **testnet**. Check the [API reference](https://docs.meddleware.co.uk/blockchain/sui/) for the canonical address tables.
43
+
44
+ ::: tip Using the Sui MCP
45
+ If you have the `sui-docs` MCP configured (`https://sui.mcp.kapa.ai`), consult it for current Sui framework types, SDK patterns, and deprecation notices before writing Move or TypeScript.
46
+ :::
@@ -0,0 +1,136 @@
1
+ # PTB patterns
2
+
3
+ Programmable Transaction Blocks (PTBs) let you compose multiple Move calls into a single atomic transaction. The Meddleware apps use PTBs for every multi-step on-chain operation.
4
+
5
+ ## Basic structure
6
+
7
+ ```ts
8
+ import { Transaction } from '@mysten/sui/transactions'
9
+ import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'
10
+
11
+ const client = new SuiClient({ url: getFullnodeUrl('testnet') })
12
+ const tx = new Transaction()
13
+
14
+ // Add Move calls, object inputs, and coin splits here
15
+ // ...
16
+
17
+ // Sign and execute (with a wallet or a keypair)
18
+ const result = await client.signAndExecuteTransaction({
19
+ transaction: tx,
20
+ signer: keypair,
21
+ options: { showEffects: true, showObjectChanges: true },
22
+ })
23
+ ```
24
+
25
+ ## Common patterns
26
+
27
+ ### Calling a Move function
28
+
29
+ ```ts
30
+ tx.moveCall({
31
+ target: `${PACKAGE_ID}::${MODULE}::${FUNCTION}`,
32
+ arguments: [
33
+ tx.object(objectId), // pass an existing on-chain object
34
+ tx.pure.u64(1000n), // pass a scalar
35
+ tx.pure.address(recipientAddr),
36
+ ],
37
+ })
38
+ ```
39
+
40
+ ### Splitting coins for a payment
41
+
42
+ ```ts
43
+ const [coin] = tx.splitCoins(tx.gas, [tx.pure.u64(feeMist)])
44
+ tx.moveCall({
45
+ target: `${PACKAGE}::module::pay`,
46
+ arguments: [coin],
47
+ })
48
+ ```
49
+
50
+ ### Multi-step composition (example: deposit → allocate)
51
+
52
+ ```ts
53
+ // 1. Split exact SUI amount
54
+ const [depositCoin] = tx.splitCoins(tx.gas, [tx.pure.u64(amountMist)])
55
+
56
+ // 2. Deposit into vault (returns mwSUI shares)
57
+ const [shares] = tx.moveCall({
58
+ target: `${VAULT_PACKAGE}::vault_core::deposit`,
59
+ arguments: [depositCoin, tx.object(VAULT_ID), tx.object(CONFIG_ID)],
60
+ })
61
+
62
+ // 3. Transfer shares to caller (or keep for further use)
63
+ tx.transferObjects([shares], tx.pure.address(callerAddress))
64
+ ```
65
+
66
+ ### Reading a result across calls
67
+
68
+ Results from `moveCall` are returned as `TransactionResult` values. Pass them to subsequent calls:
69
+
70
+ ```ts
71
+ const [mintedNft] = tx.moveCall({ target: `${PKG}::nft::mint`, arguments: [...] })
72
+ tx.moveCall({
73
+ target: `${PKG}::vault::deposit_nft`,
74
+ arguments: [mintedNft], // result used as argument
75
+ })
76
+ ```
77
+
78
+ ## Dry-run before sending
79
+
80
+ ```ts
81
+ const dryRunResult = await client.dryRunTransaction({
82
+ transaction: await tx.build({ client }),
83
+ })
84
+ if (dryRunResult.effects.status.status !== 'success') {
85
+ console.error('Dry-run failed:', dryRunResult.effects.status.error)
86
+ }
87
+ ```
88
+
89
+ ## Setting gas budget
90
+
91
+ ```ts
92
+ tx.setGasBudget(20_000_000n) // 0.02 SUI; adjust to operation complexity
93
+ ```
94
+
95
+ For complex multi-step PTBs (rebalance, multi-strategy allocation), use 100–200 MIST × number of strategy steps as a baseline and dry-run to confirm.
96
+
97
+ ## Wallet integration
98
+
99
+ In a Vue app using `@mysten/dapp-kit`:
100
+
101
+ ```ts
102
+ import { useCurrentAccount, useSignAndExecuteTransaction } from '@mysten/dapp-kit'
103
+
104
+ const account = useCurrentAccount()
105
+ const { mutate: signAndExecute } = useSignAndExecuteTransaction()
106
+
107
+ function executeMyPTB() {
108
+ const tx = new Transaction()
109
+ // ... build tx ...
110
+ signAndExecute(
111
+ { transaction: tx },
112
+ {
113
+ onSuccess: (result) => { console.log('digest:', result.digest) },
114
+ onError: (err) => { console.error(err) },
115
+ },
116
+ )
117
+ }
118
+ ```
119
+
120
+ ## Error handling
121
+
122
+ Move aborts are surfaced as numeric codes in `effects.status.error`. Map them to human-readable messages for your users:
123
+
124
+ ```ts
125
+ const VAULT_ERRORS: Record<number, string> = {
126
+ 1: 'Insufficient balance',
127
+ 5: 'Below minimum deposit',
128
+ 85: 'Stale strategy NAV — refresh all active strategy NAVs in the same PTB',
129
+ }
130
+
131
+ function describeAbort(error: string): string {
132
+ const match = error.match(/MoveAbort\(.*?,\s*(\d+)\)/)
133
+ const code = match ? parseInt(match[1]) : -1
134
+ return VAULT_ERRORS[code] ?? `Unknown error (code ${code})`
135
+ }
136
+ ```
@@ -0,0 +1,49 @@
1
+ # Sealed Storage — SDK setup
2
+
3
+ [User docs →](https://docs.meddleware.co.uk/blockchain/sui/sealed-storage/) | [API reference →](https://docs.meddleware.co.uk/blockchain/sui/sealed-storage/reference)
4
+
5
+ Sealed Storage combines Walrus blob storage with Seal's decentralised key management and on-chain Move access-control policies. Only addresses that satisfy the policy can decrypt.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @meddleware/seal-client @meddleware/walrus-client @mysten/seal @mysten/sui
11
+ ```
12
+
13
+ ## Initialise the client
14
+
15
+ ```ts
16
+ import { SealClient } from '@meddleware/seal-client'
17
+ import { WalrusRelayClient } from '@meddleware/walrus-client'
18
+ import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'
19
+
20
+ const suiClient = new SuiClient({ url: getFullnodeUrl('testnet') })
21
+ const walrus = new WalrusRelayClient({ relayUrl: RELAY_URL, suiClient, network: 'testnet' })
22
+ const sealClient = new SealClient({ suiClient, network: 'testnet' })
23
+ ```
24
+
25
+ ## Three-step flow
26
+
27
+ Sealed Storage works in three steps:
28
+
29
+ 1. **Encrypt** — the client encrypts the data locally using a Seal key derived from the policy ID.
30
+ 2. **Store** — the encrypted ciphertext is uploaded to Walrus; a manifest records the `blobId` and policy address.
31
+ 3. **Decrypt** — the reader presents the manifest; Seal verifies their wallet satisfies the policy and returns the decryption key.
32
+
33
+ See [Integration guide](./integration) for the full code walkthrough and [Writing policies](./policies) for policy authoring.
34
+
35
+ ## Manifest shape
36
+
37
+ ```ts
38
+ interface SealManifest {
39
+ version: 1
40
+ policyId: string // Move object ID of the access policy
41
+ blobId: string // Walrus blob ID of the ciphertext
42
+ encryptedKey: string // base64 — Seal-encrypted data key
43
+ contentType: string
44
+ }
45
+ ```
46
+
47
+ The manifest itself is public — store it anywhere (Walrus, on-chain, your database). It contains no plaintext.
48
+
49
+ <!-- white-label: operator guide for running a custom Seal key server committee — planned -->
@@ -0,0 +1,125 @@
1
+ # Sealed Storage — Integration guide
2
+
3
+ ## Full encrypt → store → decrypt flow
4
+
5
+ ### Encrypt and store
6
+
7
+ ```ts
8
+ import { SealClient } from '@meddleware/seal-client'
9
+ import { WalrusRelayClient } from '@meddleware/walrus-client'
10
+
11
+ async function encryptAndStore(
12
+ sealClient: SealClient,
13
+ walrus: WalrusRelayClient,
14
+ file: File,
15
+ policyId: string, // on-chain Move policy object ID
16
+ epochs = 5,
17
+ ): Promise<SealManifest> {
18
+ // 1. Encrypt locally — key is derived from policyId
19
+ const { ciphertext, encryptedKey } = await sealClient.encrypt(file, { policyId })
20
+
21
+ // 2. Upload ciphertext to Walrus
22
+ const result = await walrus.store(
23
+ new File([ciphertext], file.name, { type: 'application/octet-stream' }),
24
+ { epochs },
25
+ )
26
+
27
+ // 3. Return the manifest (store this somewhere accessible)
28
+ return {
29
+ version: 1,
30
+ policyId,
31
+ blobId: result.blobId,
32
+ encryptedKey,
33
+ contentType: file.type,
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### Decrypt
39
+
40
+ ```ts
41
+ async function decryptManifest(
42
+ sealClient: SealClient,
43
+ walrus: WalrusRelayClient,
44
+ manifest: SealManifest,
45
+ signer: { address: string; sign: (msg: Uint8Array) => Promise<Uint8Array> },
46
+ ): Promise<Blob> {
47
+ // 1. Request the decryption key from Seal
48
+ // Seal verifies on-chain that signer.address satisfies the policy
49
+ const key = await sealClient.requestKey({
50
+ policyId: manifest.policyId,
51
+ encryptedKey: manifest.encryptedKey,
52
+ signer,
53
+ })
54
+
55
+ // 2. Download the ciphertext from Walrus
56
+ const ciphertext = await walrus.read(manifest.blobId)
57
+
58
+ // 3. Decrypt locally
59
+ const plaintext = await sealClient.decrypt(ciphertext, key)
60
+ return new Blob([plaintext], { type: manifest.contentType })
61
+ }
62
+ ```
63
+
64
+ ## Using with dapp-kit
65
+
66
+ Wire the wallet signer from `@mysten/dapp-kit`:
67
+
68
+ ```ts
69
+ import { useCurrentAccount, useSignPersonalMessage } from '@mysten/dapp-kit'
70
+
71
+ const account = useCurrentAccount()
72
+ const { mutateAsync: signPersonalMessage } = useSignPersonalMessage()
73
+
74
+ const signer = {
75
+ address: account.value!.address,
76
+ sign: async (msg: Uint8Array) => {
77
+ const { signature } = await signPersonalMessage({ message: msg })
78
+ return signature
79
+ },
80
+ }
81
+ ```
82
+
83
+ ## Error handling
84
+
85
+ ```ts
86
+ try {
87
+ const key = await sealClient.requestKey({ policyId, encryptedKey, signer })
88
+ } catch (err) {
89
+ if (err instanceof SealPolicyError) {
90
+ // Wallet does not satisfy the policy
91
+ console.error('Access denied:', err.policyId, err.reason)
92
+ } else if (err instanceof SealKeyServerError) {
93
+ // Key server unavailable or quorum not reached
94
+ console.error('Key server error:', err.message)
95
+ }
96
+ throw err
97
+ }
98
+ ```
99
+
100
+ ## Vue composable
101
+
102
+ ```ts
103
+ // composables/useSealDecrypt.ts
104
+ import { ref } from 'vue'
105
+
106
+ export function useSealDecrypt(sealClient: SealClient, walrus: WalrusRelayClient) {
107
+ const decrypting = ref(false)
108
+ const result = ref<Blob | null>(null)
109
+ const error = ref<Error | null>(null)
110
+
111
+ async function decrypt(manifest: SealManifest, signer: SealSigner) {
112
+ decrypting.value = true
113
+ error.value = null
114
+ try {
115
+ result.value = await decryptManifest(sealClient, walrus, manifest, signer)
116
+ } catch (e) {
117
+ error.value = e as Error
118
+ } finally {
119
+ decrypting.value = false
120
+ }
121
+ }
122
+
123
+ return { decrypting, result, error, decrypt }
124
+ }
125
+ ```
@@ -0,0 +1,81 @@
1
+ # Writing policies
2
+
3
+ Access policies are Move objects that implement the Seal policy interface. The `seal_policies_sui` package provides built-in policy types; you can also author custom policies.
4
+
5
+ ## Built-in policy types
6
+
7
+ | Policy | Condition for access |
8
+ | --- | --- |
9
+ | `AllowlistPolicy` | Address is on an explicit allowlist managed by the policy owner |
10
+ | `NftGatePolicy` | Address holds a qualifying NFT from a specified gate |
11
+ | `TimeLockPolicy` | Current epoch ≥ unlock epoch |
12
+ | `ThresholdPolicy` | m-of-n signers from a configured set |
13
+
14
+ ## NFT gate policy (most common)
15
+
16
+ Create a policy that requires an active Access Gate pass:
17
+
18
+ ```move
19
+ // seal_policies_sui::nft_gate_policy
20
+ public fun create(
21
+ gate_id: ID, // access_gate::Gate object ID
22
+ ctx: &mut TxContext,
23
+ ): NftGatePolicy
24
+ ```
25
+
26
+ In TypeScript, after publishing the policy, record its object ID in your manifest.
27
+
28
+ ```ts
29
+ import { Transaction } from '@mysten/sui/transactions'
30
+
31
+ const tx = new Transaction()
32
+ const [policy] = tx.moveCall({
33
+ target: `${SEAL_POLICIES_PACKAGE}::nft_gate_policy::create`,
34
+ arguments: [tx.pure.id(GATE_OBJECT_ID)],
35
+ })
36
+ tx.transferObjects([policy], tx.pure.address(ownerAddress))
37
+ await client.signAndExecuteTransaction({ transaction: tx, signer })
38
+ ```
39
+
40
+ ## Time-lock policy
41
+
42
+ ```ts
43
+ const [policy] = tx.moveCall({
44
+ target: `${SEAL_POLICIES_PACKAGE}::time_lock_policy::create`,
45
+ arguments: [tx.pure.u64(unlockEpoch)],
46
+ })
47
+ ```
48
+
49
+ ## Custom policy
50
+
51
+ A policy must expose a public `approve` function that Seal calls during key request:
52
+
53
+ ```move
54
+ module my_policy::my_policy {
55
+ use sui::tx_context::TxContext;
56
+
57
+ public struct MyPolicy has key, store {
58
+ id: UID,
59
+ // your policy state
60
+ }
61
+
62
+ /// Seal calls this to check whether the requester is permitted.
63
+ /// Abort if access is denied.
64
+ public fun approve(policy: &MyPolicy, requester: address, _ctx: &TxContext) {
65
+ assert!(is_allowed(policy, requester), EAccessDenied);
66
+ }
67
+ }
68
+ ```
69
+
70
+ The `approve` function is the only required interface. Any state can live in the policy object.
71
+
72
+ ## Policy lifecycle
73
+
74
+ - Policies are shared or owned objects on-chain.
75
+ - The creator controls mutation (e.g. adding to an allowlist) via the object's admin cap.
76
+ - Revoking access for an allowlist policy: remove the address via the admin function.
77
+ - Policies cannot be retroactively applied to already-encrypted blobs — encryption and the policy are bound at encrypt time.
78
+
79
+ ::: warning Pre-mainnet requirement
80
+ The Seal key server committee for mainnet is not yet formed. Testnet policies work against the testnet Seal committee. See [DEFERRED_WORK.md](https://github.com/meddleware-org/vault) for the mainnet committee timeline.
81
+ :::
@@ -0,0 +1,67 @@
1
+ # Walrus Storage — SDK setup
2
+
3
+ [User docs →](https://docs.meddleware.co.uk/blockchain/sui/walrus-storage/) | [API reference →](https://docs.meddleware.co.uk/blockchain/sui/walrus-storage/reference)
4
+
5
+ `@meddleware/walrus-client` wraps the Walrus blob store and the Meddleware relay layer: upload blobs, read them back, extend their storage lifetime, and enumerate blobs owned by an address.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @meddleware/walrus-client @mysten/walrus @mysten/sui
11
+ ```
12
+
13
+ ## Initialise the client
14
+
15
+ ```ts
16
+ import { WalrusRelayClient } from '@meddleware/walrus-client'
17
+ import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'
18
+
19
+ const suiClient = new SuiClient({ url: getFullnodeUrl('testnet') })
20
+
21
+ const walrus = new WalrusRelayClient({
22
+ relayUrl: import.meta.env.VITE_WALRUS_RELAY_URL ?? 'https://walrus-relay.meddleware.co.uk',
23
+ suiClient,
24
+ network: 'testnet',
25
+ })
26
+ ```
27
+
28
+ The relay URL points at a Meddleware-hosted Walrus relay. For a gated relay, the client constructs an NFT gate proof automatically when you supply a wallet signer (see [Integration guide](./integration)).
29
+
30
+ ## Core types
31
+
32
+ ```ts
33
+ // Blob storage result
34
+ interface BlobStoreResult {
35
+ blobId: string
36
+ expiryEpoch: number
37
+ cost: bigint // in MIST
38
+ newlyCreated: boolean
39
+ }
40
+
41
+ // Blob metadata
42
+ interface BlobInfo {
43
+ blobId: string
44
+ size: number
45
+ expiryEpoch: number
46
+ owner: string
47
+ }
48
+ ```
49
+
50
+ ## Quick upload
51
+
52
+ ```ts
53
+ const file = new File(['Hello, Walrus!'], 'hello.txt', { type: 'text/plain' })
54
+ const result = await walrus.store(file, { epochs: 5 })
55
+ console.log('Stored at blobId:', result.blobId)
56
+ ```
57
+
58
+ ## Quick read
59
+
60
+ ```ts
61
+ const data = await walrus.read(blobId)
62
+ const text = new TextDecoder().decode(data)
63
+ ```
64
+
65
+ See [Integration guide](./integration) for detailed patterns, error handling, and wallet signing.
66
+
67
+ <!-- white-label: operator customization guide (relay domain, tip config, NFT gate setup) — planned -->