@meddleware/walrus-client 0.0.1 → 0.0.2
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 +10 -4
- package/src/access.ts +43 -49
- package/src/client.ts +29 -3
- package/src/upload.ts +41 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/walrus-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Walrus decentralised storage client and asset management utilities for Sui applications.",
|
|
6
6
|
"author": "MeddleWare <meddleware@proton.me>",
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/meddleware-org/walrus-client.git"
|
|
11
11
|
},
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"walrus",
|
|
14
|
+
"sui",
|
|
15
|
+
"decentralised-storage",
|
|
16
|
+
"web3"
|
|
17
|
+
],
|
|
13
18
|
"files": [
|
|
14
19
|
"src",
|
|
15
20
|
"CHANGELOG.md"
|
|
@@ -24,7 +29,8 @@
|
|
|
24
29
|
},
|
|
25
30
|
"dependencies": {
|
|
26
31
|
"@mysten/sui": "~2.17.0",
|
|
27
|
-
"@mysten/walrus": "~1.1.7"
|
|
32
|
+
"@mysten/walrus": "~1.1.7",
|
|
33
|
+
"@meddleware/nft-gate-client": "~0.0.3"
|
|
28
34
|
},
|
|
29
35
|
"devDependencies": {
|
|
30
36
|
"@types/node": "~24.12.2",
|
|
@@ -34,4 +40,4 @@
|
|
|
34
40
|
"publishConfig": {
|
|
35
41
|
"access": "public"
|
|
36
42
|
}
|
|
37
|
-
}
|
|
43
|
+
}
|
package/src/access.ts
CHANGED
|
@@ -1,76 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NFT-gated relay access helpers.
|
|
3
3
|
*
|
|
4
|
-
* When the
|
|
4
|
+
* When the Meddleware upload relay runs behind an `nft-gate` auth gateway, an upload must
|
|
5
5
|
* carry a wallet-signed **access proof** proving the caller holds the required access NFT.
|
|
6
6
|
* These helpers build that proof; the resulting token is passed to
|
|
7
7
|
* `createWalrusClient({ uploadRelayAuthToken })`, which threads it as the relay
|
|
8
8
|
* `Authorization: Bearer` header (see `client.ts`).
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* verifies these proofs.
|
|
14
|
-
*
|
|
15
|
-
* TODO(unify): once `@meddleware/nft-gate-client` is published (from the `nft-gate`
|
|
16
|
-
* standalone workspace, canonical vault location `blockchain/sui/packages/nft-gate-client-sui/`),
|
|
17
|
-
* import `fetchChallenge`/`buildAccessProof`/`personalMessageForNonce` from it and delete the
|
|
18
|
-
* duplicated logic below (keep this module as the thin Walrus-facing wrapper).
|
|
10
|
+
* Core primitives are imported from `@meddleware/nft-gate-client` (the canonical package).
|
|
11
|
+
* This module re-exports them under Walrus-friendly aliases and adds the one-shot
|
|
12
|
+
* `createRelayAccessToken` convenience function.
|
|
19
13
|
*/
|
|
20
14
|
|
|
15
|
+
import {
|
|
16
|
+
type Challenge,
|
|
17
|
+
type AccessProof,
|
|
18
|
+
type PersonalMessageSigner,
|
|
19
|
+
personalMessageForNonce,
|
|
20
|
+
encodeAccessProof,
|
|
21
|
+
fetchChallenge,
|
|
22
|
+
} from '@meddleware/nft-gate-client'
|
|
23
|
+
|
|
24
|
+
// ── Re-exports under Walrus-client aliases ────────────────────────────────────
|
|
25
|
+
|
|
21
26
|
/** A server-issued, time-bound challenge from the gateway's `GET /v1/challenge`. */
|
|
22
|
-
export
|
|
23
|
-
nonce: string
|
|
24
|
-
expiresAt: number
|
|
25
|
-
}
|
|
27
|
+
export type RelayChallenge = Challenge
|
|
26
28
|
|
|
27
29
|
/** The proof payload; `consumeDigest` is present only for single-use gates. */
|
|
28
|
-
export
|
|
29
|
-
address: string
|
|
30
|
-
nonce: string
|
|
31
|
-
signature: string
|
|
32
|
-
consumeDigest?: string
|
|
33
|
-
}
|
|
30
|
+
export type AccessProofInput = AccessProof
|
|
34
31
|
|
|
35
32
|
/** A wallet personal-message signer (e.g. wallet-standard `sui:signPersonalMessage`). */
|
|
36
|
-
export type
|
|
33
|
+
export type { PersonalMessageSigner }
|
|
37
34
|
|
|
38
35
|
/** The exact bytes the wallet signs for a nonce. MUST match the gateway's derivation. */
|
|
39
|
-
export
|
|
40
|
-
return new TextEncoder().encode(`nft-gate:access:${nonce}`)
|
|
41
|
-
}
|
|
36
|
+
export { personalMessageForNonce }
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Encode a proof as the base64(JSON) Bearer token the relay auth header carries.
|
|
40
|
+
*
|
|
41
|
+
* @throws {Error} if `JSON.stringify` or `btoa` is unavailable in the environment.
|
|
42
|
+
*/
|
|
48
43
|
export function buildAccessProofToken(proof: AccessProofInput): string {
|
|
49
|
-
|
|
50
|
-
address: proof.address,
|
|
51
|
-
nonce: proof.nonce,
|
|
52
|
-
signature: proof.signature,
|
|
53
|
-
}
|
|
54
|
-
if (proof.consumeDigest) payload.consumeDigest = proof.consumeDigest
|
|
55
|
-
return toBase64(JSON.stringify(payload))
|
|
44
|
+
return encodeAccessProof(proof)
|
|
56
45
|
}
|
|
57
46
|
|
|
58
|
-
/**
|
|
59
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Fetch a fresh challenge from the gateway's `GET /v1/challenge` endpoint.
|
|
49
|
+
* Tolerates both `expiresAt` (camelCase) and `expires_at` (snake_case) response shapes.
|
|
50
|
+
*
|
|
51
|
+
* @throws {Error} if the network request fails or the gateway returns a non-2xx status.
|
|
52
|
+
* @throws {Error} if the response body is missing the required `nonce` field.
|
|
53
|
+
*/
|
|
54
|
+
export function fetchRelayChallenge(
|
|
60
55
|
relayHost: string,
|
|
61
56
|
opts: { signal?: AbortSignal } = {},
|
|
62
57
|
): Promise<RelayChallenge> {
|
|
63
|
-
|
|
64
|
-
if (!res.ok) throw new Error(`challenge request failed: ${res.status}`)
|
|
65
|
-
const data = (await res.json()) as { nonce?: string; expiresAt?: number; expires_at?: number }
|
|
66
|
-
if (!data || typeof data.nonce !== 'string') throw new Error('challenge response missing nonce')
|
|
67
|
-
return { nonce: data.nonce, expiresAt: data.expiresAt ?? data.expires_at ?? 0 }
|
|
58
|
+
return fetchChallenge(relayHost, opts)
|
|
68
59
|
}
|
|
69
60
|
|
|
70
61
|
/**
|
|
71
|
-
* One-shot: fetch a challenge, sign it with the wallet, and return the
|
|
72
|
-
* `createWalrusClient({ uploadRelayAuthToken })`. For single-use gates, supply
|
|
73
|
-
* `consumeDigest` of the on-chain consume transaction
|
|
62
|
+
* One-shot: fetch a challenge, sign it with the wallet, and return the encoded proof token
|
|
63
|
+
* to pass as `createWalrusClient({ uploadRelayAuthToken })`. For single-use gates, supply
|
|
64
|
+
* the `consumeDigest` of the on-chain consume transaction.
|
|
65
|
+
*
|
|
66
|
+
* @throws {Error} if the challenge fetch fails.
|
|
67
|
+
* @throws {Error} if the wallet signer rejects the message.
|
|
74
68
|
*/
|
|
75
69
|
export async function createRelayAccessToken(opts: {
|
|
76
70
|
relayHost: string
|
|
@@ -79,13 +73,13 @@ export async function createRelayAccessToken(opts: {
|
|
|
79
73
|
consumeDigest?: string
|
|
80
74
|
signal?: AbortSignal
|
|
81
75
|
}): Promise<string> {
|
|
82
|
-
const challenge = await
|
|
76
|
+
const challenge = await fetchChallenge(opts.relayHost, { signal: opts.signal })
|
|
83
77
|
const message = personalMessageForNonce(challenge.nonce)
|
|
84
78
|
const { signature } = await opts.sign(message)
|
|
85
|
-
return
|
|
79
|
+
return encodeAccessProof({
|
|
86
80
|
address: opts.address,
|
|
87
81
|
nonce: challenge.nonce,
|
|
88
82
|
signature,
|
|
89
|
-
consumeDigest: opts.consumeDigest,
|
|
83
|
+
...(opts.consumeDigest ? { consumeDigest: opts.consumeDigest } : {}),
|
|
90
84
|
})
|
|
91
85
|
}
|
package/src/client.ts
CHANGED
|
@@ -3,8 +3,10 @@ import { walrus, TESTNET_WALRUS_PACKAGE_CONFIG, MAINNET_WALRUS_PACKAGE_CONFIG }
|
|
|
3
3
|
|
|
4
4
|
export { TESTNET_WALRUS_PACKAGE_CONFIG, MAINNET_WALRUS_PACKAGE_CONFIG }
|
|
5
5
|
|
|
6
|
+
/** Supported Walrus network environments. */
|
|
6
7
|
export type WalrusNetwork = 'testnet' | 'mainnet'
|
|
7
8
|
|
|
9
|
+
/** Default Sui full-node RPC URLs, keyed by network. */
|
|
8
10
|
export const DEFAULT_RPC_URLS: Record<WalrusNetwork, string> = {
|
|
9
11
|
testnet: 'https://fullnode.testnet.sui.io:443',
|
|
10
12
|
mainnet: 'https://fullnode.mainnet.sui.io:443',
|
|
@@ -40,27 +42,51 @@ export function walrusBlobUrl(
|
|
|
40
42
|
return `${host}/v1/blobs/${blobId}`
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
/** Return the Walrus on-chain package config for the given network. */
|
|
43
46
|
export function getWalrusPackageConfig(network: WalrusNetwork) {
|
|
44
47
|
return network === 'mainnet' ? MAINNET_WALRUS_PACKAGE_CONFIG : TESTNET_WALRUS_PACKAGE_CONFIG
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
/** Options accepted by {@link createWalrusClient}. */
|
|
47
51
|
export type CreateWalrusClientOptions = {
|
|
52
|
+
/** Target network (default `'testnet'`). */
|
|
48
53
|
network?: WalrusNetwork
|
|
49
54
|
/** Override the Sui JSON-RPC/gRPC fullnode URL. Defaults to the public Mysten endpoint for the network. */
|
|
50
55
|
rpcUrl?: string
|
|
56
|
+
/** Optional WASM bundle URL for the Walrus WASM client. */
|
|
51
57
|
wasmUrl?: string
|
|
58
|
+
/**
|
|
59
|
+
* Upload relay host URL. Defaults to the public Mysten relay for the network.
|
|
60
|
+
* Operators running their own relay (e.g. Meddleware's NFT-gated relay) should
|
|
61
|
+
* pass their relay URL here alongside `uploadRelayAuthToken`.
|
|
62
|
+
*/
|
|
52
63
|
uploadRelayHost?: string
|
|
64
|
+
/**
|
|
65
|
+
* Bearer token for the upload relay `Authorization` header. Obtain via
|
|
66
|
+
* `createRelayAccessToken` from the `access` module when the relay is NFT-gated.
|
|
67
|
+
*/
|
|
53
68
|
uploadRelayAuthToken?: string
|
|
69
|
+
/**
|
|
70
|
+
* Maximum tip payment to the upload relay in MIST (default 1,000,000 = 0.001 SUI).
|
|
71
|
+
* The relay may request less; this cap prevents the client from overpaying.
|
|
72
|
+
*/
|
|
54
73
|
uploadRelayMaxTipMist?: number
|
|
55
74
|
/**
|
|
56
75
|
* When true, build a client with NO upload relay (direct-to-storage-node).
|
|
57
|
-
* Overrides `uploadRelayHost` and the default
|
|
58
|
-
*
|
|
59
|
-
* Walrus storage nodes.
|
|
76
|
+
* Overrides `uploadRelayHost` and the default relay fallback. Useful for
|
|
77
|
+
* server-side Node.js scripts where relay infra is unnecessary.
|
|
60
78
|
*/
|
|
61
79
|
disableUploadRelay?: boolean
|
|
62
80
|
}
|
|
63
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Create a Walrus client pre-configured for the given network and relay options.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const client = createWalrusClient({ network: 'testnet' })
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
64
90
|
export function createWalrusClient({
|
|
65
91
|
network = 'testnet',
|
|
66
92
|
rpcUrl,
|
package/src/upload.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { WalrusFile } from '@mysten/walrus'
|
|
|
3
3
|
import type { Signer } from '@mysten/sui/cryptography'
|
|
4
4
|
import type { createWalrusClient } from './client.js'
|
|
5
5
|
|
|
6
|
+
/** The return type of {@link createWalrusClient}. */
|
|
6
7
|
export type WalrusClient = ReturnType<typeof createWalrusClient>
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -23,17 +24,30 @@ export const MAX_SINGLE_RESERVATION_EPOCHS = 53
|
|
|
23
24
|
*/
|
|
24
25
|
export const LONG_TERM_EPOCHS = 200
|
|
25
26
|
|
|
27
|
+
/** Options shared by all upload helpers. */
|
|
26
28
|
export type UploadOptions = {
|
|
29
|
+
/** Storage epochs to reserve (default: {@link MAX_SINGLE_RESERVATION_EPOCHS}). */
|
|
27
30
|
epochs?: number
|
|
31
|
+
/** Whether the blob can be deleted by its owner (default false). */
|
|
28
32
|
deletable?: boolean
|
|
33
|
+
/** Arbitrary key-value metadata tags stored with the blob. */
|
|
29
34
|
tags?: Record<string, string>
|
|
30
35
|
}
|
|
31
36
|
|
|
37
|
+
/** The object IDs returned after a successful upload. */
|
|
32
38
|
export type UploadResult = {
|
|
39
|
+
/** Walrus content-addressed blob ID. */
|
|
33
40
|
blobId: string
|
|
41
|
+
/** On-chain Walrus blob object ID. */
|
|
34
42
|
blobObjectId: string
|
|
35
43
|
}
|
|
36
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Upload raw bytes as a Walrus quilt (file bundle). Use this from Node.js with a keypair
|
|
47
|
+
* signer; use {@link createUploadFlow} in the browser for wallet-popup-safe signing.
|
|
48
|
+
*
|
|
49
|
+
* @throws {Error} if the Walrus write transaction fails or the signer rejects.
|
|
50
|
+
*/
|
|
37
51
|
export async function uploadBytes(
|
|
38
52
|
client: WalrusClient,
|
|
39
53
|
contents: Uint8Array,
|
|
@@ -53,7 +67,11 @@ export async function uploadBytes(
|
|
|
53
67
|
return { blobId: result.blobId, blobObjectId: result.id }
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Read a local file and upload it as a Walrus quilt. Node.js only.
|
|
72
|
+
*
|
|
73
|
+
* @throws {Error} if the file cannot be read or the upload fails.
|
|
74
|
+
*/
|
|
57
75
|
export async function uploadLocalFile(
|
|
58
76
|
client: WalrusClient,
|
|
59
77
|
filePath: string,
|
|
@@ -65,7 +83,10 @@ export async function uploadLocalFile(
|
|
|
65
83
|
return uploadBytes(client, new Uint8Array(contents), identifier, signer, options)
|
|
66
84
|
}
|
|
67
85
|
|
|
68
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Begin a multi-step quilt upload flow suitable for browser wallet signing.
|
|
88
|
+
* The caller drives the flow: encode → register (wallet signs) → upload → certify (wallet signs).
|
|
89
|
+
*/
|
|
69
90
|
export function createUploadFlow(
|
|
70
91
|
client: WalrusClient,
|
|
71
92
|
contents: Uint8Array,
|
|
@@ -83,7 +104,12 @@ export function createUploadFlow(
|
|
|
83
104
|
// wallets/explorers), use a RAW blob: `GET /v1/blobs/<blobId>` returns the exact
|
|
84
105
|
// bytes. See `walrusBlobUrl()` in ./client.
|
|
85
106
|
|
|
86
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Upload raw bytes as a Walrus raw blob (served directly at `/v1/blobs/<blobId>`).
|
|
109
|
+
* Use this for assets that must be URL-addressable (e.g. token icons). Node.js only.
|
|
110
|
+
*
|
|
111
|
+
* @throws {Error} if the Walrus write transaction fails or the signer rejects.
|
|
112
|
+
*/
|
|
87
113
|
export async function uploadImageBytes(
|
|
88
114
|
client: WalrusClient,
|
|
89
115
|
contents: Uint8Array,
|
|
@@ -100,12 +126,18 @@ export async function uploadImageBytes(
|
|
|
100
126
|
return { blobId: res.blobId, blobObjectId: res.blobObject.id }
|
|
101
127
|
}
|
|
102
128
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Begin a multi-step raw-blob upload flow suitable for browser wallet signing.
|
|
131
|
+
*
|
|
132
|
+
* Drive the returned flow:
|
|
133
|
+
* ```ts
|
|
134
|
+
* await flow.encode()
|
|
135
|
+
* const regTx = flow.register({ owner, epochs, deletable }) // wallet signs + executes
|
|
136
|
+
* await flow.upload({ digest }) // digest of regTx
|
|
137
|
+
* const certTx = flow.certify() // wallet signs + executes
|
|
138
|
+
* const { blobId } = await flow.getBlob() // use walrusBlobUrl(network, blobId)
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
109
141
|
export function createBlobUploadFlow(client: WalrusClient, contents: Uint8Array) {
|
|
110
142
|
return client.walrus.writeBlobFlow({ blob: contents })
|
|
111
143
|
}
|