@meddleware/nft-gate-client 0.0.8 → 0.0.10
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/dist/proof.d.ts +0 -1
- package/package.json +2 -2
- package/src/ownership.ts +3 -2
- package/src/proof.ts +37 -3
package/dist/proof.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type { AccessProof, Challenge } from './types.js';
|
|
|
6
6
|
export declare function personalMessageForNonce(nonce: string): Uint8Array;
|
|
7
7
|
/** Encode a proof as the compact Bearer token carried in the relay auth header. */
|
|
8
8
|
export declare function encodeAccessProof(proof: AccessProof): string;
|
|
9
|
-
/** Decode a proof token produced by {@link encodeAccessProof}. Throws on malformed input. */
|
|
10
9
|
export declare function decodeAccessProof(token: string): AccessProof;
|
|
11
10
|
/** A wallet-provided personal-message signer (e.g. wallet-standard `sui:signPersonalMessage`). */
|
|
12
11
|
export type PersonalMessageSigner = (message: Uint8Array) => Promise<{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/nft-gate-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"homepage": "https://docs.meddleware.co.uk/blockchain/sui/access-gate/",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Client-side helpers for the access_gate NFT access primitive: ownership queries, purchase/consume PTB builders, challenge signing, and access-proof assembly.",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"test:integration": "vitest run --config vitest.integration.config.ts"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@mysten/sui": "^2.
|
|
40
|
+
"@mysten/sui": "^2.31.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "~24.12.2",
|
package/src/ownership.ts
CHANGED
|
@@ -27,8 +27,9 @@ function parseUsesRemaining(variant: any): number | null {
|
|
|
27
27
|
const fields = variant.fields ?? variant
|
|
28
28
|
const ur = fields?.uses_remaining ?? fields?.SingleUse?.uses_remaining
|
|
29
29
|
if (ur != null) return Number(ur)
|
|
30
|
-
//
|
|
31
|
-
|
|
30
|
+
// Either an unknown tag, or a SingleUse whose count is missing from this node's rendering — in
|
|
31
|
+
// both cases the remaining count is unknown, so report null (never a fabricated 0).
|
|
32
|
+
return null
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/** True if `type` names an `access_gate` NFT struct (transferable or soulbound). */
|
package/src/proof.ts
CHANGED
|
@@ -8,12 +8,29 @@ export function personalMessageForNonce(nonce: string): Uint8Array {
|
|
|
8
8
|
return new TextEncoder().encode(`nft-gate:access:${nonce}`)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// UTF-8-safe base64. The naive `btoa(str)` operates on Latin-1 and throws/corrupts on any code
|
|
12
|
+
// point > 0xFF, so we round-trip through UTF-8 bytes first. For pure-ASCII payloads (the only kind
|
|
13
|
+
// this wire format carries — see the ASCII contract below) the output is byte-identical to `btoa`,
|
|
14
|
+
// so the gateways' decoders are unaffected.
|
|
11
15
|
function toBase64(s: string): string {
|
|
12
|
-
|
|
16
|
+
const bytes = new TextEncoder().encode(s)
|
|
17
|
+
let bin = ''
|
|
18
|
+
for (const b of bytes) bin += String.fromCharCode(b)
|
|
19
|
+
return btoa(bin)
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
function fromBase64(s: string): string {
|
|
16
|
-
|
|
23
|
+
const bin = atob(s)
|
|
24
|
+
const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0))
|
|
25
|
+
return new TextDecoder().decode(bytes)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** True iff every character is ASCII (code point ≤ 0x7F). */
|
|
29
|
+
function isAscii(s: string): boolean {
|
|
30
|
+
for (let i = 0; i < s.length; i++) {
|
|
31
|
+
if (s.charCodeAt(i) > 0x7f) return false
|
|
32
|
+
}
|
|
33
|
+
return true
|
|
17
34
|
}
|
|
18
35
|
|
|
19
36
|
/** Encode a proof as the compact Bearer token carried in the relay auth header. */
|
|
@@ -21,12 +38,29 @@ export function encodeAccessProof(proof: AccessProof): string {
|
|
|
21
38
|
return toBase64(JSON.stringify(proof))
|
|
22
39
|
}
|
|
23
40
|
|
|
24
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Decode a proof token produced by {@link encodeAccessProof}. Throws on malformed input.
|
|
43
|
+
*
|
|
44
|
+
* ASCII contract: both gateways issue ASCII-only nonces (`<region>.<hex>` or hex) and treat
|
|
45
|
+
* `address`/`nonce` as ASCII end-to-end. We enforce that here so a non-ASCII field is rejected
|
|
46
|
+
* rather than silently diverging between the client and a gateway verifier. `address` is emitted
|
|
47
|
+
* verbatim (raw); canonicalisation is owned gateway-side (see nft-gate `verify.rs`/`verify.ts`), so
|
|
48
|
+
* the wire format is unchanged and the golden vector stays stable.
|
|
49
|
+
*/
|
|
50
|
+
/** Maximum encoded token length (bytes). Prevents a multi-MB blob from saturating `atob`/`JSON.parse`. */
|
|
51
|
+
const MAX_TOKEN_BYTES = 4096
|
|
52
|
+
|
|
25
53
|
export function decodeAccessProof(token: string): AccessProof {
|
|
54
|
+
if (token.length > MAX_TOKEN_BYTES) {
|
|
55
|
+
throw new Error(`access proof token too large (${token.length} > ${MAX_TOKEN_BYTES})`)
|
|
56
|
+
}
|
|
26
57
|
const raw = JSON.parse(fromBase64(token)) as Partial<AccessProof>
|
|
27
58
|
if (!raw || typeof raw.address !== 'string' || typeof raw.nonce !== 'string' || typeof raw.signature !== 'string') {
|
|
28
59
|
throw new Error('malformed access proof')
|
|
29
60
|
}
|
|
61
|
+
if (!isAscii(raw.address) || !isAscii(raw.nonce) || !isAscii(raw.signature)) {
|
|
62
|
+
throw new Error('non-ASCII field in access proof')
|
|
63
|
+
}
|
|
30
64
|
const proof: AccessProof = { address: raw.address, nonce: raw.nonce, signature: raw.signature }
|
|
31
65
|
if (typeof raw.consumeDigest === 'string') proof.consumeDigest = raw.consumeDigest
|
|
32
66
|
return proof
|