@bwalletx/connect 0.4.1 → 0.5.0
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 +9 -0
- package/assets/protocol/401.jpg +0 -0
- package/assets/protocol/401.mp4 +0 -0
- package/assets/protocol/402.jpg +0 -0
- package/assets/protocol/402.mp4 +0 -0
- package/assets/protocol/403.jpg +0 -0
- package/assets/protocol/403.mp4 +0 -0
- package/dist/access-Z6xQwKVF.d.cts +118 -0
- package/dist/access-Z6xQwKVF.d.ts +118 -0
- package/dist/bwalletx-connect.global.js +17 -17
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/{chunk-NNMUSWPG.js → chunk-CVLXFNAG.js} +123 -1
- package/dist/chunk-CVLXFNAG.js.map +1 -0
- package/dist/{chunk-FUQ46RJV.js → chunk-GF6GUDZL.js} +2 -2
- package/dist/chunk-MWD4BCO5.js +182 -0
- package/dist/chunk-MWD4BCO5.js.map +1 -0
- package/dist/{chunk-7NAKOJGZ.js → chunk-QRXFL66W.js} +220 -10
- package/dist/chunk-QRXFL66W.js.map +1 -0
- package/dist/element.cjs +209 -0
- package/dist/element.cjs.map +1 -1
- package/dist/element.js +2 -1
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +292 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +377 -9
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +52 -2
- package/dist/next.d.ts +52 -2
- package/dist/next.js +92 -1
- package/dist/next.js.map +1 -1
- package/dist/react.cjs +209 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +3 -2
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +406 -9
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +111 -1
- package/dist/server.d.ts +111 -1
- package/dist/server.js +47 -1
- package/package.json +10 -9
- package/dist/chunk-7NAKOJGZ.js.map +0 -1
- package/dist/chunk-NNMUSWPG.js.map +0 -1
- /package/dist/{chunk-FUQ46RJV.js.map → chunk-GF6GUDZL.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
- **Access protocol v1 ($401 identity / $403 token-gated access)**, spec in `docs/ACCESS-PROTOCOL.md`. Standard BRC-100 calls only, so any BRC-100 wallet works.
|
|
6
|
+
- Client: `requestAccess()` signs the site's challenge and proves the keys that hold the token (`proveHoldings()`). Errors are `AccessError` with a protocol code.
|
|
7
|
+
- Server: `createAccessChallenge`, `verifyAccess`, `checkTokenHolding`, plus `oneSatResolver()` (1Sat indexer) and `bitSignResolver()` (identity level / KYC). Fails closed: an indexer outage is `indexer_unavailable`, never a grant.
|
|
8
|
+
- Next.js: `createBwalletXAccess()` gives challenge, verify, status and logout routes, a signed access cookie, and `getAccess()`, which re-checks holdings every `recheckEvery` seconds so selling the tokens ends access.
|
|
9
|
+
- `<bwalletx-signin require-token="bsv21:…" min="100">` runs the access flow and emits `bwalletx-access-granted` / `bwalletx-access-denied`.
|
|
10
|
+
- Protocol videos and posters in `assets/protocol/` (401 green, 402 blue, 403 red), exported as `PROTOCOL_MEDIA`.
|
|
11
|
+
|
|
3
12
|
## 0.4.1 (2026-10-12)
|
|
4
13
|
|
|
5
14
|
- React: `BwalletXProvider` and `BwalletxSignin` declare `ReactElement` return types, so they type-check as JSX under React 18 `@types/react`.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { WalletProtocol } from '@bsv/sdk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* $401 / $402 / $403 access protocol, version 1. Shared by sites (client + server) and wallets.
|
|
5
|
+
* The full spec is docs/ACCESS-PROTOCOL.md. Pure: no I/O, @bsv/sdk types only.
|
|
6
|
+
*
|
|
7
|
+
* 401 identity "prove who you are (at identity level N, or KYC'd)"
|
|
8
|
+
* 402 payment "pay for this" (the challenge format is reserved; payment itself uses pay())
|
|
9
|
+
* 403 access "prove you hold N of token X (or N basis points of its supply)"
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare const ACCESS_VERSION: 1;
|
|
13
|
+
/** Security level 2, a protocol name used for nothing else. Wallets recognise it and show the kind's video. */
|
|
14
|
+
declare const ACCESS_PROTOCOL: WalletProtocol;
|
|
15
|
+
/** How long a challenge is valid. */
|
|
16
|
+
declare const ACCESS_CHALLENGE_LIFETIME_S = 120;
|
|
17
|
+
/** At most this many holding proofs are read from one response. */
|
|
18
|
+
declare const MAX_HOLDING_PROOFS = 50;
|
|
19
|
+
type AccessKind = '401' | '402' | '403';
|
|
20
|
+
interface TokenRequirement {
|
|
21
|
+
/** 'bsv21:<txid>_<vout>' */
|
|
22
|
+
id: string;
|
|
23
|
+
/** Minimum raw (unscaled, integer string) amount. */
|
|
24
|
+
min?: string;
|
|
25
|
+
/** Minimum in basis points of total supply (1 = 0.01%, 10000 = 100%). */
|
|
26
|
+
minBps?: number;
|
|
27
|
+
}
|
|
28
|
+
interface AccessRequirement {
|
|
29
|
+
token?: TokenRequirement;
|
|
30
|
+
/** $401 identity level (bit-sign strands): 1 Basic, 2 Verified, 3 Strong, 4 Sovereign (Veriff). */
|
|
31
|
+
identityLevel?: number;
|
|
32
|
+
/** A live KYC credential is required. */
|
|
33
|
+
kyc?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** What the wallet signs, as canonical JSON. Times are seconds since the epoch. */
|
|
36
|
+
interface AccessChallenge {
|
|
37
|
+
v: 1;
|
|
38
|
+
kind: AccessKind;
|
|
39
|
+
origin: string;
|
|
40
|
+
nonce: string;
|
|
41
|
+
iat: number;
|
|
42
|
+
exp: number;
|
|
43
|
+
/** The identity key the site expects. A different wallet account is `wrong_account`. */
|
|
44
|
+
sub?: string;
|
|
45
|
+
require: AccessRequirement;
|
|
46
|
+
}
|
|
47
|
+
/** A key that locks some of the required token, proving control by signing the same challenge bytes. */
|
|
48
|
+
interface HoldingProof {
|
|
49
|
+
/** Compressed public key hex of the key locking the outputs. */
|
|
50
|
+
pubkey: string;
|
|
51
|
+
/** DER signature by that key over SHA-256(challenge bytes), as BRC-100 createSignature returns. */
|
|
52
|
+
signature: number[];
|
|
53
|
+
/** Optional hint: outpoints ('txid_vout') this key holds. Servers must not trust it. */
|
|
54
|
+
outpoints?: string[];
|
|
55
|
+
}
|
|
56
|
+
/** What the wallet (via requestAccess) posts back to the site. */
|
|
57
|
+
interface AccessResponse {
|
|
58
|
+
v: 1;
|
|
59
|
+
/** The challenge string exactly as issued. */
|
|
60
|
+
challenge: string;
|
|
61
|
+
identityKey: string;
|
|
62
|
+
/** DER signature: createSignature({ protocolID: ACCESS_PROTOCOL, keyID: nonce, counterparty: 'anyone', data: challenge bytes }). */
|
|
63
|
+
signature: number[];
|
|
64
|
+
holdings?: HoldingProof[];
|
|
65
|
+
}
|
|
66
|
+
type AccessErrorCode = 'not_installed' | 'user_rejected' | 'wrong_account' | 'insufficient_tokens' | 'identity_level_too_low' | 'kyc_required' | 'indexer_unavailable' | 'expired' | 'replay' | 'invalid_signature' | 'bad_request';
|
|
67
|
+
/** The HTTP status a site should answer each code with. */
|
|
68
|
+
declare const ACCESS_ERROR_STATUS: Record<AccessErrorCode, number>;
|
|
69
|
+
interface AccessErrorBody {
|
|
70
|
+
error: AccessErrorCode;
|
|
71
|
+
message: string;
|
|
72
|
+
/** insufficient_tokens: raw amounts; identity_level_too_low: levels. */
|
|
73
|
+
have?: string | number;
|
|
74
|
+
need?: string | number;
|
|
75
|
+
}
|
|
76
|
+
declare class AccessError extends Error {
|
|
77
|
+
readonly code: AccessErrorCode;
|
|
78
|
+
readonly have?: string | number;
|
|
79
|
+
readonly need?: string | number;
|
|
80
|
+
constructor(code: AccessErrorCode, message?: string, extra?: {
|
|
81
|
+
have?: string | number;
|
|
82
|
+
need?: string | number;
|
|
83
|
+
});
|
|
84
|
+
toJSON(): AccessErrorBody;
|
|
85
|
+
}
|
|
86
|
+
/** One video + poster + colour per kind, so wallets and sites show the same thing. Paths are inside the package. */
|
|
87
|
+
declare const PROTOCOL_MEDIA: Record<AccessKind, {
|
|
88
|
+
name: string;
|
|
89
|
+
color: string;
|
|
90
|
+
video: string;
|
|
91
|
+
poster: string;
|
|
92
|
+
}>;
|
|
93
|
+
/** Media URLs on a CDN (unpkg) for a given package version. */
|
|
94
|
+
declare function protocolMediaUrl(kind: AccessKind, which: 'video' | 'poster', version?: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Canonical JSON: object keys sorted by code unit, no whitespace, undefined members dropped.
|
|
97
|
+
* Only strings, finite numbers, booleans, null, arrays and plain objects are allowed.
|
|
98
|
+
*/
|
|
99
|
+
declare function canonicalJson(v: unknown): string;
|
|
100
|
+
/** The bytes every signature in the protocol covers: UTF-8 of the challenge string. */
|
|
101
|
+
declare function challengeBytes(challenge: string): number[];
|
|
102
|
+
/** Throws AccessError('bad_request') if the requirement is malformed. Returns a normalised copy. */
|
|
103
|
+
declare function normaliseRequirement(r: unknown): AccessRequirement;
|
|
104
|
+
/** The kind a requirement implies: any token → 403; otherwise 401. */
|
|
105
|
+
declare function kindFor(r: AccessRequirement): AccessKind;
|
|
106
|
+
/** Parse and validate a challenge string. Throws AccessError('bad_request'). Does not check expiry. */
|
|
107
|
+
declare function parseAccessChallenge(s: unknown): AccessChallenge;
|
|
108
|
+
/** Raw amount needed for a token requirement, given total supply (needed only for minBps). Rounds up. */
|
|
109
|
+
declare function tokenNeedRaw(t: TokenRequirement, supplyRaw?: string): string;
|
|
110
|
+
/** Compare unscaled integer strings. Fails closed on malformed input. */
|
|
111
|
+
declare function atLeastRaw(have: string, need: string): boolean;
|
|
112
|
+
/** A user-readable line for an error code. */
|
|
113
|
+
declare function accessErrorMessage(code: AccessErrorCode, e?: {
|
|
114
|
+
have?: string | number;
|
|
115
|
+
need?: string | number;
|
|
116
|
+
}): string;
|
|
117
|
+
|
|
118
|
+
export { AccessError as A, type HoldingProof as H, MAX_HOLDING_PROOFS as M, PROTOCOL_MEDIA as P, type TokenRequirement as T, type AccessRequirement as a, type AccessResponse as b, ACCESS_ERROR_STATUS as c, ACCESS_PROTOCOL as d, ACCESS_VERSION as e, type AccessChallenge as f, type AccessErrorBody as g, type AccessErrorCode as h, type AccessKind as i, accessErrorMessage as j, canonicalJson as k, ACCESS_CHALLENGE_LIFETIME_S as l, atLeastRaw as m, challengeBytes as n, kindFor as o, protocolMediaUrl as p, normaliseRequirement as q, parseAccessChallenge as r, tokenNeedRaw as t };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { WalletProtocol } from '@bsv/sdk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* $401 / $402 / $403 access protocol, version 1. Shared by sites (client + server) and wallets.
|
|
5
|
+
* The full spec is docs/ACCESS-PROTOCOL.md. Pure: no I/O, @bsv/sdk types only.
|
|
6
|
+
*
|
|
7
|
+
* 401 identity "prove who you are (at identity level N, or KYC'd)"
|
|
8
|
+
* 402 payment "pay for this" (the challenge format is reserved; payment itself uses pay())
|
|
9
|
+
* 403 access "prove you hold N of token X (or N basis points of its supply)"
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare const ACCESS_VERSION: 1;
|
|
13
|
+
/** Security level 2, a protocol name used for nothing else. Wallets recognise it and show the kind's video. */
|
|
14
|
+
declare const ACCESS_PROTOCOL: WalletProtocol;
|
|
15
|
+
/** How long a challenge is valid. */
|
|
16
|
+
declare const ACCESS_CHALLENGE_LIFETIME_S = 120;
|
|
17
|
+
/** At most this many holding proofs are read from one response. */
|
|
18
|
+
declare const MAX_HOLDING_PROOFS = 50;
|
|
19
|
+
type AccessKind = '401' | '402' | '403';
|
|
20
|
+
interface TokenRequirement {
|
|
21
|
+
/** 'bsv21:<txid>_<vout>' */
|
|
22
|
+
id: string;
|
|
23
|
+
/** Minimum raw (unscaled, integer string) amount. */
|
|
24
|
+
min?: string;
|
|
25
|
+
/** Minimum in basis points of total supply (1 = 0.01%, 10000 = 100%). */
|
|
26
|
+
minBps?: number;
|
|
27
|
+
}
|
|
28
|
+
interface AccessRequirement {
|
|
29
|
+
token?: TokenRequirement;
|
|
30
|
+
/** $401 identity level (bit-sign strands): 1 Basic, 2 Verified, 3 Strong, 4 Sovereign (Veriff). */
|
|
31
|
+
identityLevel?: number;
|
|
32
|
+
/** A live KYC credential is required. */
|
|
33
|
+
kyc?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** What the wallet signs, as canonical JSON. Times are seconds since the epoch. */
|
|
36
|
+
interface AccessChallenge {
|
|
37
|
+
v: 1;
|
|
38
|
+
kind: AccessKind;
|
|
39
|
+
origin: string;
|
|
40
|
+
nonce: string;
|
|
41
|
+
iat: number;
|
|
42
|
+
exp: number;
|
|
43
|
+
/** The identity key the site expects. A different wallet account is `wrong_account`. */
|
|
44
|
+
sub?: string;
|
|
45
|
+
require: AccessRequirement;
|
|
46
|
+
}
|
|
47
|
+
/** A key that locks some of the required token, proving control by signing the same challenge bytes. */
|
|
48
|
+
interface HoldingProof {
|
|
49
|
+
/** Compressed public key hex of the key locking the outputs. */
|
|
50
|
+
pubkey: string;
|
|
51
|
+
/** DER signature by that key over SHA-256(challenge bytes), as BRC-100 createSignature returns. */
|
|
52
|
+
signature: number[];
|
|
53
|
+
/** Optional hint: outpoints ('txid_vout') this key holds. Servers must not trust it. */
|
|
54
|
+
outpoints?: string[];
|
|
55
|
+
}
|
|
56
|
+
/** What the wallet (via requestAccess) posts back to the site. */
|
|
57
|
+
interface AccessResponse {
|
|
58
|
+
v: 1;
|
|
59
|
+
/** The challenge string exactly as issued. */
|
|
60
|
+
challenge: string;
|
|
61
|
+
identityKey: string;
|
|
62
|
+
/** DER signature: createSignature({ protocolID: ACCESS_PROTOCOL, keyID: nonce, counterparty: 'anyone', data: challenge bytes }). */
|
|
63
|
+
signature: number[];
|
|
64
|
+
holdings?: HoldingProof[];
|
|
65
|
+
}
|
|
66
|
+
type AccessErrorCode = 'not_installed' | 'user_rejected' | 'wrong_account' | 'insufficient_tokens' | 'identity_level_too_low' | 'kyc_required' | 'indexer_unavailable' | 'expired' | 'replay' | 'invalid_signature' | 'bad_request';
|
|
67
|
+
/** The HTTP status a site should answer each code with. */
|
|
68
|
+
declare const ACCESS_ERROR_STATUS: Record<AccessErrorCode, number>;
|
|
69
|
+
interface AccessErrorBody {
|
|
70
|
+
error: AccessErrorCode;
|
|
71
|
+
message: string;
|
|
72
|
+
/** insufficient_tokens: raw amounts; identity_level_too_low: levels. */
|
|
73
|
+
have?: string | number;
|
|
74
|
+
need?: string | number;
|
|
75
|
+
}
|
|
76
|
+
declare class AccessError extends Error {
|
|
77
|
+
readonly code: AccessErrorCode;
|
|
78
|
+
readonly have?: string | number;
|
|
79
|
+
readonly need?: string | number;
|
|
80
|
+
constructor(code: AccessErrorCode, message?: string, extra?: {
|
|
81
|
+
have?: string | number;
|
|
82
|
+
need?: string | number;
|
|
83
|
+
});
|
|
84
|
+
toJSON(): AccessErrorBody;
|
|
85
|
+
}
|
|
86
|
+
/** One video + poster + colour per kind, so wallets and sites show the same thing. Paths are inside the package. */
|
|
87
|
+
declare const PROTOCOL_MEDIA: Record<AccessKind, {
|
|
88
|
+
name: string;
|
|
89
|
+
color: string;
|
|
90
|
+
video: string;
|
|
91
|
+
poster: string;
|
|
92
|
+
}>;
|
|
93
|
+
/** Media URLs on a CDN (unpkg) for a given package version. */
|
|
94
|
+
declare function protocolMediaUrl(kind: AccessKind, which: 'video' | 'poster', version?: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Canonical JSON: object keys sorted by code unit, no whitespace, undefined members dropped.
|
|
97
|
+
* Only strings, finite numbers, booleans, null, arrays and plain objects are allowed.
|
|
98
|
+
*/
|
|
99
|
+
declare function canonicalJson(v: unknown): string;
|
|
100
|
+
/** The bytes every signature in the protocol covers: UTF-8 of the challenge string. */
|
|
101
|
+
declare function challengeBytes(challenge: string): number[];
|
|
102
|
+
/** Throws AccessError('bad_request') if the requirement is malformed. Returns a normalised copy. */
|
|
103
|
+
declare function normaliseRequirement(r: unknown): AccessRequirement;
|
|
104
|
+
/** The kind a requirement implies: any token → 403; otherwise 401. */
|
|
105
|
+
declare function kindFor(r: AccessRequirement): AccessKind;
|
|
106
|
+
/** Parse and validate a challenge string. Throws AccessError('bad_request'). Does not check expiry. */
|
|
107
|
+
declare function parseAccessChallenge(s: unknown): AccessChallenge;
|
|
108
|
+
/** Raw amount needed for a token requirement, given total supply (needed only for minBps). Rounds up. */
|
|
109
|
+
declare function tokenNeedRaw(t: TokenRequirement, supplyRaw?: string): string;
|
|
110
|
+
/** Compare unscaled integer strings. Fails closed on malformed input. */
|
|
111
|
+
declare function atLeastRaw(have: string, need: string): boolean;
|
|
112
|
+
/** A user-readable line for an error code. */
|
|
113
|
+
declare function accessErrorMessage(code: AccessErrorCode, e?: {
|
|
114
|
+
have?: string | number;
|
|
115
|
+
need?: string | number;
|
|
116
|
+
}): string;
|
|
117
|
+
|
|
118
|
+
export { AccessError as A, type HoldingProof as H, MAX_HOLDING_PROOFS as M, PROTOCOL_MEDIA as P, type TokenRequirement as T, type AccessRequirement as a, type AccessResponse as b, ACCESS_ERROR_STATUS as c, ACCESS_PROTOCOL as d, ACCESS_VERSION as e, type AccessChallenge as f, type AccessErrorBody as g, type AccessErrorCode as h, type AccessKind as i, accessErrorMessage as j, canonicalJson as k, ACCESS_CHALLENGE_LIFETIME_S as l, atLeastRaw as m, challengeBytes as n, kindFor as o, protocolMediaUrl as p, normaliseRequirement as q, parseAccessChallenge as r, tokenNeedRaw as t };
|