@solarpunkltd/swarm-id 0.9.0 → 0.10.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/dist/types.d.ts CHANGED
@@ -537,10 +537,11 @@ export type UploadMode = z.infer<typeof UploadModeSchema>;
537
537
  /**
538
538
  * Why a session cannot upload, when `uploadMode` is `"unavailable"`.
539
539
  *
540
- * - `no-stamp`: the site has no stamp to use. The account has no drive (or
541
- * its pointers name only drives that are gone), the person set this site to
542
- * no drive, or the person has not yet confirmed the site's drive in the
543
- * connect window since the site signed in.
540
+ * - `no-stamp`: the site has no stamp to use. The person has not picked a
541
+ * drive for this site, the drive they picked is gone, they set this site to
542
+ * no drive, or they have not yet confirmed the site's drive in the connect
543
+ * window since the site signed in. The account's default drive never
544
+ * stands in for a missing pick.
544
545
  * - `stamper-failed` — the stamp resolved and the stamper still did not build.
545
546
  * `initializeStamper` logs and returns rather than throwing, so without this
546
547
  * the symptom is indistinguishable from having no stamp at all.
@@ -610,6 +611,11 @@ export declare const ConnectionIdentitySchema: z.ZodObject<{
610
611
  }>;
611
612
  url: z.ZodString;
612
613
  }, z.core.$strip>;
614
+ walletLink: z.ZodOptional<z.ZodObject<{
615
+ message: z.ZodString;
616
+ walletSignature: z.ZodString;
617
+ accountSignature: z.ZodString;
618
+ }, z.core.$strip>>;
613
619
  }, z.core.$strip>;
614
620
  export type ConnectionIdentity = z.infer<typeof ConnectionIdentitySchema>;
615
621
  export declare const ConnectionInfoSchema: z.ZodObject<{
@@ -646,6 +652,11 @@ export declare const ConnectionInfoSchema: z.ZodObject<{
646
652
  }>;
647
653
  url: z.ZodString;
648
654
  }, z.core.$strip>;
655
+ walletLink: z.ZodOptional<z.ZodObject<{
656
+ message: z.ZodString;
657
+ walletSignature: z.ZodString;
658
+ accountSignature: z.ZodString;
659
+ }, z.core.$strip>>;
649
660
  }, z.core.$strip>>;
650
661
  appKey: z.ZodOptional<z.ZodObject<{
651
662
  address: z.ZodString;
@@ -2412,6 +2423,11 @@ export declare const ConnectionInfoChangedMessageSchema: z.ZodObject<{
2412
2423
  }>;
2413
2424
  url: z.ZodString;
2414
2425
  }, z.core.$strip>;
2426
+ walletLink: z.ZodOptional<z.ZodObject<{
2427
+ message: z.ZodString;
2428
+ walletSignature: z.ZodString;
2429
+ accountSignature: z.ZodString;
2430
+ }, z.core.$strip>>;
2415
2431
  }, z.core.$strip>>;
2416
2432
  appKey: z.ZodOptional<z.ZodObject<{
2417
2433
  address: z.ZodString;
@@ -2777,6 +2793,11 @@ export declare const IframeToParentMessageSchema: z.ZodDiscriminatedUnion<[z.Zod
2777
2793
  }>;
2778
2794
  url: z.ZodString;
2779
2795
  }, z.core.$strip>;
2796
+ walletLink: z.ZodOptional<z.ZodObject<{
2797
+ message: z.ZodString;
2798
+ walletSignature: z.ZodString;
2799
+ accountSignature: z.ZodString;
2800
+ }, z.core.$strip>>;
2780
2801
  }, z.core.$strip>>;
2781
2802
  appKey: z.ZodOptional<z.ZodObject<{
2782
2803
  address: z.ZodString;
@@ -3070,9 +3091,15 @@ export declare const AuthDataSchema: z.ZodObject<{
3070
3091
  settings: z.ZodOptional<z.ZodObject<{
3071
3092
  appSessionDuration: z.ZodOptional<z.ZodNumber>;
3072
3093
  }, z.core.$strip>>;
3094
+ walletLink: z.ZodOptional<z.ZodObject<{
3095
+ message: z.ZodString;
3096
+ walletSignature: z.ZodString;
3097
+ accountSignature: z.ZodString;
3098
+ }, z.core.$strip>>;
3073
3099
  accountNameAt: z.ZodOptional<z.ZodNumber>;
3074
3100
  defaultStampAt: z.ZodOptional<z.ZodNumber>;
3075
3101
  settingsAt: z.ZodOptional<z.ZodNumber>;
3102
+ walletLinkAt: z.ZodOptional<z.ZodNumber>;
3076
3103
  lastModified: z.ZodOptional<z.ZodNumber>;
3077
3104
  partitionCount: z.ZodOptional<z.ZodNumber>;
3078
3105
  }, z.core.$strip>;
@@ -3185,9 +3212,15 @@ export declare const SetSecretMessageSchema: z.ZodObject<{
3185
3212
  settings: z.ZodOptional<z.ZodObject<{
3186
3213
  appSessionDuration: z.ZodOptional<z.ZodNumber>;
3187
3214
  }, z.core.$strip>>;
3215
+ walletLink: z.ZodOptional<z.ZodObject<{
3216
+ message: z.ZodString;
3217
+ walletSignature: z.ZodString;
3218
+ accountSignature: z.ZodString;
3219
+ }, z.core.$strip>>;
3188
3220
  accountNameAt: z.ZodOptional<z.ZodNumber>;
3189
3221
  defaultStampAt: z.ZodOptional<z.ZodNumber>;
3190
3222
  settingsAt: z.ZodOptional<z.ZodNumber>;
3223
+ walletLinkAt: z.ZodOptional<z.ZodNumber>;
3191
3224
  lastModified: z.ZodOptional<z.ZodNumber>;
3192
3225
  partitionCount: z.ZodOptional<z.ZodNumber>;
3193
3226
  }, z.core.$strip>;
@@ -3267,9 +3300,15 @@ export declare const PopupToIframeMessageSchema: z.ZodDiscriminatedUnion<[z.ZodO
3267
3300
  settings: z.ZodOptional<z.ZodObject<{
3268
3301
  appSessionDuration: z.ZodOptional<z.ZodNumber>;
3269
3302
  }, z.core.$strip>>;
3303
+ walletLink: z.ZodOptional<z.ZodObject<{
3304
+ message: z.ZodString;
3305
+ walletSignature: z.ZodString;
3306
+ accountSignature: z.ZodString;
3307
+ }, z.core.$strip>>;
3270
3308
  accountNameAt: z.ZodOptional<z.ZodNumber>;
3271
3309
  defaultStampAt: z.ZodOptional<z.ZodNumber>;
3272
3310
  settingsAt: z.ZodOptional<z.ZodNumber>;
3311
+ walletLinkAt: z.ZodOptional<z.ZodNumber>;
3273
3312
  lastModified: z.ZodOptional<z.ZodNumber>;
3274
3313
  partitionCount: z.ZodOptional<z.ZodNumber>;
3275
3314
  }, z.core.$strip>;
@@ -3289,7 +3328,7 @@ export interface ClientOptions {
3289
3328
  initializationTimeout?: number;
3290
3329
  /**
3291
3330
  * Invoked whenever the dApp-visible ConnectionInfo changes (identity rename,
3292
- * default-stamp change, local↔synced account migration, postage stamp added,
3331
+ * the site's drive changed, local↔synced account migration, postage stamp added,
3293
3332
  * auth transitions, etc.). Fires once on initial auth check, then on every
3294
3333
  * effective change. The current snapshot is also available synchronously
3295
3334
  * via `client.connectionInfo`.
@@ -0,0 +1,33 @@
1
+ /**
2
+ * How long a wallet's answer is kept before the chain is asked again. A name
3
+ * changed on chain shows at most this long after on a page that read it.
4
+ */
5
+ export declare const ENS_NAME_CACHE_MS: number;
6
+ /**
7
+ * How long a failed read is kept, so a page that shows many senders does not
8
+ * ask a broken or slow endpoint again for every message it renders, while a
9
+ * passing outage still clears soon.
10
+ */
11
+ export declare const ENS_NAME_FAILURE_CACHE_MS: number;
12
+ export interface EnsNameOptions {
13
+ /** An Ethereum mainnet JSON-RPC endpoint. Empty, no name is ever answered. */
14
+ rpcUrl: string;
15
+ /** Deadline for the whole lookup. Defaults to `CHAIN_READ_TIMEOUT_MS`. */
16
+ timeoutMs?: number;
17
+ }
18
+ /**
19
+ * The ENS name of `wallet`, when ENS's Universal Resolver answers a primary
20
+ * name whose address record names the wallet back, and the name is already
21
+ * ENSIP-15 normalised. Undefined for no such name and for everything that goes
22
+ * wrong: no endpoint, an endpoint that does not answer in time, an answer that
23
+ * does not decode, a name's offchain gateway that fails. Never throws.
24
+ *
25
+ * An answer, name or no name, is kept in memory for `ENS_NAME_CACHE_MS` per
26
+ * endpoint and wallet, and lookups that arrive together share one read. A
27
+ * failed read answers no name for `ENS_NAME_FAILURE_CACHE_MS`, and the first
28
+ * lookup after that asks again.
29
+ *
30
+ * @param wallet - the wallet's address, in any case, with or without `0x`,
31
+ * such as `verifyWalletLink(...).wallet`
32
+ */
33
+ export declare function resolveVerifiedEnsName(wallet: string, options: EnsNameOptions): Promise<string | undefined>;
@@ -0,0 +1,13 @@
1
+ /** The ENS registry, at the same address on every chain ENS is deployed to. */
2
+ export declare const ENS_REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
3
+ /** How long each read may take. */
4
+ export interface EnsReadOptions {
5
+ timeoutMs: number;
6
+ }
7
+ /** The EIP-137 namehash of a name already in its normalized form. */
8
+ export declare function ensNamehash(name: string): string;
9
+ /**
10
+ * The text record `key` of `name`, through the resolver the registry names.
11
+ * @throws when the name has no resolver or the answers do not decode.
12
+ */
13
+ export declare function readEnsText(rpcUrl: string, name: string, key: string, options: EnsReadOptions): Promise<string>;
@@ -8,6 +8,11 @@ export interface JsonRpcOptions {
8
8
  * RPC". Defaults to the endpoint's origin.
9
9
  */
10
10
  label?: string;
11
+ /**
12
+ * The longest response body read, in bytes. A longer body is refused as it
13
+ * streams in, before anything parses it. Unset, the body is read whole.
14
+ */
15
+ maxBodyBytes?: number;
11
16
  }
12
17
  /** One call in a batch. Ids are assigned by {@link jsonRpcBatch}. */
13
18
  export interface JsonRpcRequest {
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Sign `message` as `personal_sign` does.
3
+ *
4
+ * @param privateKey - bare hex, 32 bytes
5
+ * @returns `0x` + r ‖ s ‖ v, v 27 or 28, s low
6
+ */
7
+ export declare function personalSign(message: string, privateKey: string): string;
8
+ /**
9
+ * The address that signed `message`, `0x` + 40 lower-case hex characters.
10
+ * A valid signature over a changed message recovers SOME address, so a caller
11
+ * always compares the result with the signer it expects.
12
+ *
13
+ * @param subject - how errors name the signature, e.g. "Chat binding signature"
14
+ * @throws on a signature that is not 65 bytes of 0x hex, has v other than 27
15
+ * or 28, has a high s, or does not recover a public key.
16
+ */
17
+ export declare function recoverPersonalSigner(message: string, signature: string, subject: string): string;
@@ -1,40 +1,39 @@
1
- import { type ConnectedApp, type PostageStamp, type SignedInAccount } from "../schemas";
1
+ import { type ConnectedApp, type PostageStamp } from "../schemas";
2
2
  /**
3
- * Resolve which postage stamp an app should upload with.
3
+ * Resolve which postage stamp an app uploads with: the one the person picked
4
+ * for it (`postageStampBatchID`), and nothing else.
4
5
  *
5
- * An app may carry its own batch override (`postageStampBatchID`); otherwise it
6
- * falls back to the account's default stamp. The account owns the stamp set, so
7
- * these pointers are the single source of truth. An app set to
8
- * `NO_POSTAGE_STAMP` resolves to no stamp, the default included.
6
+ * The account default is never a fallback (Levi's ruling D4, 2026-10-16). A
7
+ * site with no pick, a site set to `NO_POSTAGE_STAMP`, and a site whose
8
+ * picked drive is gone all resolve to no stamp, and the person picks one in
9
+ * Swarm ID. Falling back let a drive bought after the site signed in, or the
10
+ * default left after the picked drive was removed, pay for the site's
11
+ * uploads without the person choosing it.
9
12
  *
10
- * The lookup is existence-checked: a pointer whose stamp is missing (e.g. a
11
- * removed or replaced batch) is skipped so resolution falls through to the next
12
- * candidate instead of failing.
13
+ * A deleted stamp (tombstone) resolves to nothing. An EXPIRED one still
14
+ * resolves: it can be renewed, and the session reports it as expired (#745).
13
15
  *
14
16
  * Paired with `stampsReachableByApp` below, which decides what a partitioned
15
- * session may HOLD while this decides what it SPENDS — over the same two
16
- * pointers, and deliberately differing on tombstones: this one skips them (a
17
- * deleted stamp cannot be spent), that one keeps them (a session learns its
18
- * stamp is deleted no other way). Change the pointer rule in one and the other
19
- * has to follow, or a session holds a stamp it cannot resolve, or resolves one
20
- * it was never handed.
17
+ * session may HOLD while this decides what it SPENDS, over the same pointer,
18
+ * and deliberately differing on tombstones: this one skips them (a deleted
19
+ * stamp cannot be spent), that one keeps them (a session learns its stamp is
20
+ * deleted no other way). Change the pointer rule in one and the other has to
21
+ * follow, or a session holds a stamp it cannot resolve, or resolves one it
22
+ * was never handed.
21
23
  */
22
- export declare function resolveStampForApp(app: Pick<ConnectedApp, "postageStampBatchID">, account: Pick<SignedInAccount, "defaultPostageStampBatchID">, stamps: PostageStamp[]): PostageStamp | undefined;
24
+ export declare function resolveStampForApp(app: Pick<ConnectedApp, "postageStampBatchID">, stamps: PostageStamp[]): PostageStamp | undefined;
23
25
  /**
24
- * The stamps a connected app's session can ever reach: the ones its pointers
25
- * name — its own batch override, and the account default it falls through to.
26
+ * The stamps a connected app's session can ever reach: the one the person
27
+ * picked for it, and none while that pick waits for the person to confirm it.
26
28
  *
27
29
  * This is what a PARTITIONED session is handed and allowed to keep (#578). It
28
30
  * holds its account view in memory, in a context embedded by an arbitrary dApp
29
- * page, and it can only ever spend the stamp `resolveStampForApp` picks — so
31
+ * page, and it can only ever spend the stamp `resolveStampForApp` picks, so
30
32
  * the rest of the collection is signer keys it has no use for. Least privilege
31
33
  * rather than a broken boundary: an unpartitioned proxy reads the same material
32
34
  * out of shared storage, but it does not have to be handed it.
33
35
  *
34
- * BOTH pointers, not just the winner: the default is the fallthrough
35
- * `resolveStampForApp` takes when the override's stamp is gone, so shipping
36
- * only the override would turn a recoverable stale pointer into a session that
37
- * cannot upload at all. Tombstones its pointers still name are kept too — a
38
- * tombstone is how the session learns the stamp it was spending is deleted.
36
+ * A tombstone the pointer still names is kept: a tombstone is how the session
37
+ * learns the stamp it was spending is deleted.
39
38
  */
40
- export declare function stampsReachableByApp(app: Pick<ConnectedApp, "postageStampBatchID" | "stampPending">, account: Pick<SignedInAccount, "defaultPostageStampBatchID">, stamps: PostageStamp[]): PostageStamp[];
39
+ export declare function stampsReachableByApp(app: Pick<ConnectedApp, "postageStampBatchID" | "stampPending">, stamps: PostageStamp[]): PostageStamp[];
@@ -8,8 +8,6 @@ export declare const DEFAULT_SITE_ALLOW_LIST_RECORD_KEY = "buzz.solarpunk.sites"
8
8
  * that read it before.
9
9
  */
10
10
  export declare const SITE_ALLOW_LIST_CACHE_MS: number;
11
- /** The ENS registry, at the same address on every chain ENS is deployed to. */
12
- export declare const ENS_REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
13
11
  /** Why a site was refused, so a caller can word its own screen. */
14
12
  export type SiteRefusal = "not-clean" | "not-listed" | "list-unavailable";
15
13
  export type SiteCheck = {
@@ -37,8 +35,6 @@ export interface SiteAllowListSettings {
37
35
  storage?: Pick<Storage, "getItem" | "setItem">;
38
36
  now?: () => number;
39
37
  }
40
- /** The EIP-137 namehash of a name already in its normalized form. */
41
- export declare function ensNamehash(name: string): string;
42
38
  /**
43
39
  * The origins a record names. Entries are separated by white space or commas.
44
40
  * An entry that is not a clean https origin is ignored and logged, so one bad
@@ -0,0 +1,70 @@
1
+ /** The first line, which also versions the format. */
2
+ export declare const WALLET_LINK_TITLE = "Swarm ID wallet link v1";
3
+ /** The second line, what the wallet's owner agrees to, read exactly. */
4
+ export declare const WALLET_LINK_STATEMENT = "I link this wallet to the Swarm ID account below, so sites may show my ENS name beside its messages.";
5
+ /**
6
+ * One spelling per signature, lower case as `personalSign` writes it, so this
7
+ * reader and the chat library's, which signs the link's bytes into each
8
+ * message, accept exactly the same links.
9
+ */
10
+ export declare const WALLET_LINK_SIGNATURE_PATTERN: RegExp;
11
+ /** A signed link as it travels and is stored. */
12
+ export interface WalletLink {
13
+ message: string;
14
+ /** The wallet's `personal_sign` of `message`, `0x` + r ‖ s ‖ v, 65 bytes. */
15
+ walletSignature: string;
16
+ /** The account key's `personal_sign` of `message`, in the same form. */
17
+ accountSignature: string;
18
+ }
19
+ /** The three fields a link message carries, as the message spells them. */
20
+ export interface WalletLinkFields {
21
+ /** The Swarm ID account's address, `0x` + 40 lower-case hex characters. */
22
+ account: string;
23
+ /** The wallet's address, in the same form. */
24
+ wallet: string;
25
+ /** ISO 8601 UTC, as `Date.prototype.toISOString` writes it. */
26
+ date: string;
27
+ }
28
+ /** A link both signatures were checked for. Its fields are what it names. */
29
+ export type VerifiedWalletLink = WalletLinkFields;
30
+ /** What a link has to name for a reader to accept it. */
31
+ export interface ExpectedWalletLink {
32
+ /** The account the reader expects, in any case, with or without `0x`. */
33
+ account: string;
34
+ }
35
+ /** Build the exact five-line message for these fields. */
36
+ export declare function buildWalletLinkMessage(fields: {
37
+ account: string;
38
+ wallet: string;
39
+ date: Date;
40
+ }): string;
41
+ /**
42
+ * Read the fields out of a link message, refusing every shape but the one
43
+ * `buildWalletLinkMessage` writes: another first line, another statement, a
44
+ * missing or extra line, a trailing newline, an address in another spelling, a date in
45
+ * another form.
46
+ */
47
+ export declare function parseWalletLinkMessage(message: string): WalletLinkFields;
48
+ /**
49
+ * Check a link and return what it names, or undefined. It is accepted only
50
+ * when the message has exactly the v1 shape, the wallet signature recovers the
51
+ * wallet the message names, the account signature recovers the account the
52
+ * message names, and that account is `expected.account`. Never throws.
53
+ *
54
+ * A wallet whose signature does not recover its own address, such as a
55
+ * smart-contract wallet (ERC-1271), cannot make a link that passes.
56
+ */
57
+ export declare function verifyWalletLink(link: WalletLink, expected: ExpectedWalletLink): VerifiedWalletLink | undefined;
58
+ /**
59
+ * Countersign a message the wallet has already signed, with the account's own
60
+ * key, after checking the message's shape, that the wallet signature recovers
61
+ * the wallet it names, and that it names this key's account.
62
+ *
63
+ * @param accountKey - the account's private key (bare hex), whose address is
64
+ * the account's `id`
65
+ * @param walletSignature - the wallet's `personal_sign` of `message`, already
66
+ * in the canonical 65-byte form with v 27 or 28
67
+ * @throws on any of those checks failing, so no link is ever stored that a
68
+ * reader would refuse.
69
+ */
70
+ export declare function countersignWalletLink(accountKey: string, message: string, walletSignature: string): WalletLink;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solarpunkltd/swarm-id",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "description": "Swarm ID library for authentication and Bee API operations",
6
6
  "main": "dist/swarm-id.esm.js",
@@ -62,6 +62,7 @@
62
62
  "@noble/curves": "1.2.0",
63
63
  "@noble/hashes": "1.3.2",
64
64
  "cafe-utility": "34.0.0",
65
+ "viem": "2.55.13",
65
66
  "zod": "4.1.13"
66
67
  },
67
68
  "devDependencies": {