@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.
- package/CHANGELOG.md +10 -0
- package/docs/.vitepress/config.ts +111 -0
- package/docs/.vitepress/env.d.ts +6 -0
- package/docs/.vitepress/theme/custom.css +63 -0
- package/docs/.vitepress/theme/index.ts +19 -0
- package/docs/design-system/components.md +111 -0
- package/docs/design-system/index.md +64 -0
- package/docs/design-system/tokens.md +136 -0
- package/docs/getting-started/index.md +52 -0
- package/docs/getting-started/local-dev.md +74 -0
- package/docs/getting-started/toolchain.md +83 -0
- package/docs/index.md +44 -0
- package/docs/sui/access-gate/gateway.md +159 -0
- package/docs/sui/access-gate/index.md +62 -0
- package/docs/sui/access-gate/integration.md +168 -0
- package/docs/sui/dao/index.md +154 -0
- package/docs/sui/environment.md +101 -0
- package/docs/sui/index.md +46 -0
- package/docs/sui/ptb-patterns.md +136 -0
- package/docs/sui/sealed-storage/index.md +49 -0
- package/docs/sui/sealed-storage/integration.md +125 -0
- package/docs/sui/sealed-storage/policies.md +81 -0
- package/docs/sui/walrus-storage/index.md +67 -0
- package/docs/sui/walrus-storage/integration.md +127 -0
- package/docs/sui/walrus-storage/relay-self-host.md +82 -0
- package/package.json +39 -0
- package/tsconfig.json +13 -0
|
@@ -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 -->
|