@solarpunkltd/swarm-id 0.8.2 → 0.10.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.
Files changed (35) hide show
  1. package/README.md +17 -7
  2. package/dist/bus/account-bus.d.ts +12 -0
  3. package/dist/bus/messages.d.ts +12 -0
  4. package/dist/bus/signaling-transport.d.ts +2 -0
  5. package/dist/chunks/ccip-BEanftc1.js +2 -0
  6. package/dist/chunks/ccip-BEanftc1.js.map +1 -0
  7. package/dist/chunks/{chat-binding-DgECjSWW.js → chat-binding-gSvKTm5l.js} +15 -15
  8. package/dist/chunks/chat-binding-gSvKTm5l.js.map +1 -0
  9. package/dist/chunks/index-BbRV2bja.js +2 -0
  10. package/dist/chunks/index-BbRV2bja.js.map +1 -0
  11. package/dist/index.d.ts +5 -1
  12. package/dist/internal.d.ts +3 -1
  13. package/dist/internal.esm.js +1 -1
  14. package/dist/internal.esm.js.map +1 -1
  15. package/dist/schemas.d.ts +59 -0
  16. package/dist/swarm-id-button-frame.d.ts +77 -0
  17. package/dist/swarm-id-client.d.ts +64 -69
  18. package/dist/swarm-id-proxy.d.ts +47 -55
  19. package/dist/swarm-id.esm.js +1 -1
  20. package/dist/swarm-id.esm.js.map +1 -1
  21. package/dist/sync/device-state.d.ts +16 -7
  22. package/dist/sync/index.d.ts +2 -1
  23. package/dist/sync/published-wallet-link.d.ts +27 -0
  24. package/dist/types.d.ts +374 -64
  25. package/dist/utils/button-view.d.ts +16 -0
  26. package/dist/utils/ens-name.d.ts +33 -0
  27. package/dist/utils/ens.d.ts +13 -0
  28. package/dist/utils/json-rpc.d.ts +5 -0
  29. package/dist/utils/personal-sign.d.ts +17 -0
  30. package/dist/utils/postage-stamp-association.d.ts +24 -25
  31. package/dist/utils/site-allow-list.d.ts +0 -4
  32. package/dist/utils/storage-probe.d.ts +20 -10
  33. package/dist/utils/wallet-link.d.ts +70 -0
  34. package/package.json +2 -1
  35. package/dist/chunks/chat-binding-DgECjSWW.js.map +0 -1
@@ -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
@@ -1,13 +1,16 @@
1
1
  /**
2
- * Whether the proxy iframe can read the trusted domain's first-party store —
2
+ * Whether a Swarm ID frame can read the trusted domain's first-party store,
3
3
  * asked BEFORE authentication, because it decides which transport the auth
4
- * popup has to take (#613).
4
+ * popup has to take (#613). Both frames a site embeds read it: the session
5
+ * frame, to tell the client whether `connect()` can work, and the button
6
+ * frame, to decide whether to leave the popup's challenge in storage.
5
7
  *
6
8
  * The mirror image of the probe the connect popup already runs: there, the
7
- * iframe writes a challenge and the popup checks whether it can read it back
8
- * first-party (`ui/src/lib/stores/connect.svelte.ts`). That answer arrives far
9
- * too late to choose a transport, so the same question is asked the other way
10
- * round — the first-party app leaves a marker, and the iframe reads it back.
9
+ * button frame writes a challenge and the popup checks whether it can read it
10
+ * back first-party (`ui/src/lib/stores/connect.svelte.ts`). That answer
11
+ * arrives far too late to choose a transport, so the same question is asked
12
+ * the other way round: the first-party app leaves a marker, and the frames
13
+ * read it back.
11
14
  *
12
15
  * Deliberately NOT any of these:
13
16
  *
@@ -24,7 +27,14 @@
24
27
  * The marker only ever answers YES with certainty. Absent means "not proven
25
28
  * shared" — a partitioned iframe, or a browser that has never had the SwarmID
26
29
  * site open at the top level — and the caller treats that as partitioned,
27
- * which is the safe direction: the delegated transport works in both modes.
30
+ * which is the safe direction: the handover through the Swarm ID button works
31
+ * in both modes.
32
+ *
33
+ * The marker is sealed, because the keys in that store are: a frame that sees
34
+ * the store but not the IndexedDB key that opens it sees no session in it.
35
+ * Playwright's WebKit gives a cross-site frame exactly that, one
36
+ * `localStorage` and its own IndexedDB (measured 2026-10-15), and a plain
37
+ * marker had it wait for a storage write it could never read.
28
38
  */
29
39
  export declare const STORAGE_SHARED_KEY = "swarm-id-first-party";
30
40
  /**
@@ -35,6 +45,6 @@ export declare const STORAGE_SHARED_KEY = "swarm-id-first-party";
35
45
  * bucket and make the iframe report itself shared — the one wrong answer this
36
46
  * must never give.
37
47
  */
38
- export declare function markFirstPartyStorage(): void;
39
- /** Whether this context can see the marker a first-party page left. */
40
- export declare function isStorageShared(): boolean;
48
+ export declare function markFirstPartyStorage(): Promise<void>;
49
+ /** Whether this context can see, and open, the marker a first-party page left. */
50
+ export declare function isStorageShared(): Promise<boolean>;
@@ -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.8.2",
3
+ "version": "0.10.0",
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": {