{"id":48986859,"url":"https://github.com/pkcprotocol/pkc-js","last_synced_at":"2026-05-17T09:10:08.371Z","repository":{"id":39748155,"uuid":"449483022","full_name":"pkcprotocol/pkc-js","owner":"pkcprotocol","description":"A Javascript API to build p2p social applications using PKC protocol","archived":false,"fork":false,"pushed_at":"2026-05-15T10:40:42.000Z","size":32818,"stargazers_count":72,"open_issues_count":30,"forks_count":11,"subscribers_count":6,"default_branch":"master","last_synced_at":"2026-05-15T12:30:42.206Z","etag":null,"topics":["content-sharing","decentralized-p2p","distributed","ipfs","javascript","nodejs","p2p","peer-to-peer","pkc","pkc-js","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pkcprotocol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2022-01-18T23:40:42.000Z","updated_at":"2026-05-15T10:40:46.000Z","dependencies_parsed_at":"2024-05-05T13:25:29.758Z","dependency_job_id":"2c34ccba-e8c9-48f0-a9d1-0425321dffc6","html_url":"https://github.com/pkcprotocol/pkc-js","commit_stats":{"total_commits":2041,"total_committers":6,"mean_commits":340.1666666666667,"dds":"0.23468887800097993","last_synced_commit":"ae991eeb4f2ccc4ba3cceb692dc8252b43362a3e"},"previous_names":["pkcprotocol/pkc-js","plebbit/plebbit-js"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/pkcprotocol/pkc-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkcprotocol%2Fpkc-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkcprotocol%2Fpkc-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkcprotocol%2Fpkc-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkcprotocol%2Fpkc-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pkcprotocol","download_url":"https://codeload.github.com/pkcprotocol/pkc-js/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkcprotocol%2Fpkc-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33131446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T06:27:06.342Z","status":"ssl_error","status_checked_at":"2026-05-17T06:26:59.432Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["content-sharing","decentralized-p2p","distributed","ipfs","javascript","nodejs","p2p","peer-to-peer","pkc","pkc-js","typescript"],"created_at":"2026-04-18T13:06:33.149Z","updated_at":"2026-05-17T09:10:08.350Z","avatar_url":"https://github.com/pkcprotocol.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"*Telegram group for this repo https://t.me/pkc_js*\n\n`pkc-js` is the TypeScript SDK for PKC (Public Key Communities), a serverless, decentralized social protocol built on IPFS, IPNS, and libp2p pubsub. It powers all PKC clients: CLI, Electron desktop, and web.\n\n### Glossary:\n\n- CID: https://docs.ipfs.io/concepts/content-addressing/\n- IPNS: https://docs.ipfs.io/concepts/ipns/#example-ipns-setup-with-cli\n- IPNS name: hash of a public key, the private key is used by community owners for signing IPNS records, and by authors for signing posts and comments\n- Pubsub topic: the string to publish/subscribe to in the pubsub https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/PUBSUB.md#ipfspubsubsubscribetopic-handler-options and https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#topic-membership\n- IPNS record: https://github.com/ipfs/specs/blob/master/IPNS.md#ipns-record\n- IPNS signature: https://github.com/ipfs/notes/issues/249\n- PKC signature types: https://github.com/pkcprotocol/pkc-js/blob/master/docs/signatures.md\n- PKC encryption types: https://github.com/pkcprotocol/pkc-js/blob/master/docs/encryption.md\n\nNote: IPFS files are immutable, fetched by their CID, which is a hash of their content. IPNS records are mutable, fetched by their IPNS name, which is the hash of a public key. The private key's owner can update the content. Always use IPFS files over IPNS records when possible because they are much faster to fetch.\n\n### Schema:\n\n```js\nAddress: string // a PKC author, community or multisub \"address\" can be a crypto domain like memes.bso, an IPNS name, an ethereum address, etc.\nPublication {\n  author: Author\n  communityPublicKey: string // IPNS public key of the community this publication is directed to\n  communityName?: string // optional crypto domain name of the community (e.g. 'memes.bso')\n  // note: communityAddress is available as a convenience instance property at runtime, but is not part of the wire format\n  timestamp: number // number in seconds\n  signature: Signature // sign immutable fields like author, title, content, timestamp to prevent tampering\n  protocolVersion: '1.0.0' // semantic version of the protocol https://semver.org/\n}\nComment extends Publication /* (IPFS file) */ {\n  parentCid?: string // same as postCid for top level comments, undefined for posts\n  content?: string\n  title?: string\n  link?: string\n  linkWidth?: number // author can optionally provide dimensions of image/video link which helps UI clients with infinite scrolling feeds\n  linkHeight?: number\n  linkHtmlTagName?: 'a' | 'img' | 'video' | 'audio' // author can optionally provide the HTML element to use for the link\n  spoiler?: boolean\n  nsfw?: boolean\n  flairs?: Flair[] // arbitrary colored strings added by the author or mods to describe the author or comment\n  quotedCids?: string[] // CIDs of comments being quoted/referenced in this reply\n  // below are added by community owner, not author\n  previousCid?: string // each comment/post is a linked list of other comments/posts with same comment.depth and comment.parentCid, undefined if first comment in list\n  postCid?: string // helps faster loading post info for reply direct linking, undefined for posts, a post can't know its own CID\n  depth: number // 0 = post, 1 = top level reply, 2+ = nested reply\n  thumbnailUrl?: string // optionally fetched by community owner, some web pages have thumbnail urls in their meta tags https://moz.com/blog/meta-data-templates-123\n  thumbnailUrlWidth?: number // community owner can optionally provide dimensions of thumbails which helps UI clients with infinite scrolling feeds\n  thumbnailUrlHeight?: number\n}\nVote extends Publication {\n  commentCid: string\n  vote: 1 | -1 | 0 // 0 is needed to cancel a vote\n}\nCommentEdit extends Publication {\n  commentCid: string\n  content?: string\n  deleted?: boolean\n  flairs?: Flair[]\n  spoiler?: boolean\n  nsfw?: boolean\n  reason?: string\n}\nCommentModeration extends Publication {\n  commentCid: string\n  commentModeration: {\n    flairs?: Flair[]\n    spoiler?: boolean\n    nsfw?: boolean\n    pinned?: boolean\n    locked?: boolean\n    archived?: boolean\n    approved?: boolean\n    removed?: boolean\n    purged?: boolean\n    reason?: string\n    author?: {\n      flairs?: Flair[]\n      banExpiresAt?: number\n    }\n  }\n}\nCommunityEdit extends Publication {\n  communityEdit: CreateCommunityOptions\n}\nMultisubEdit extends CreateMultisubOptions, Publication {} // not yet implemented\nCommentUpdate /* (IPFS file) */ {\n  cid: string // cid of the comment, need it in signature to prevent attack\n  edit?: AuthorCommentEdit // most recent edit by comment author, commentUpdate.edit.content, commentUpdate.edit.deleted, commentUpdate.edit.flairs override Comment instance props. Validate commentUpdate.edit.signature\n  upvoteCount: number\n  downvoteCount: number\n  replies?: Pages // only preload page 1 sorted by 'best', might preload more later, only provide sorting for posts (not comments) that have 100+ child comments\n  replyCount: number\n  childCount?: number // the total of direct children of the comment, does not include indirect children\n  number?: number // sequential comment number assigned by the community\n  postNumber?: number // sequential post number assigned by the community\n  flairs?: Flair[] // arbitrary colored strings to describe the comment, added by mods, override comment.flairs and comment.edit.flairs (which are added by author)\n  spoiler?: boolean\n  nsfw?: boolean\n  pinned?: boolean\n  locked?: boolean\n  archived?: boolean\n  approved?: boolean // if comment was pending approval and it got approved or disapproved\n  removed?: boolean // mod deleted a comment\n  reason?: string // reason the mod took a mod action\n  updatedAt: number // timestamp in seconds the CommentUpdate was updated\n  protocolVersion: '1.0.0' // semantic version of the protocol https://semver.org/\n  signature: Signature // signature of the CommentUpdate by the community owner to protect against malicious gateway\n  author?: { // add commentUpdate.author.community to comment.author.community, override comment.author.flairs with commentUpdate.author.community.flairs if any\n    community: CommunityAuthor\n  }\n  lastReplyTimestamp?: number // the timestamp of the most recent direct or indirect child of the comment\n  lastChildCid?: string // the CID of the most recent direct child of the comment\n}\nAuthor {\n  address: string\n  shortAddress: string // not part of IPFS files, added to `Author` instance as convenience. Copy address, if address is a hash, remove hash prefix and trim to 12 first chars\n  name?: string // author chosen username\n  previousCommentCid?: string // linked list of the author's comments\n  displayName?: string\n  wallets?: {[chainTicker: string]: Wallet}\n  avatar?: Nft\n  flairs?: Flair[] // (added by author originally, can be overridden by commentUpdate.author.community.flairs)\n  community?: CommunityAuthor // (added by CommentUpdate) up to date author properties specific to the community it's in\n}\nCommunityAuthor {\n  banExpiresAt?: number // (added by moderator only) timestamp in second, if defined the author was banned for this comment\n  flairs?: Flair[] // (added by moderator only) for when a mod wants to edit an author's flairs\n  postScore: number // total post karma in the community\n  replyScore: number // total reply karma in the community\n  lastCommentCid: string // last comment by the author in the community, can be used with author.previousCommentCid to get a recent author comment history in all communities\n  firstCommentTimestamp: number // timestamp of the first comment by the author in the community, used for account age based challenges\n}\nWallet {\n  address: string\n  timestamp: number // in seconds, allows partial blocking multiple authors using the same wallet\n  signature: Signature // type 'eip191' {domainSeparator:\"plebbit-author-wallet\",authorAddress:\"${authorAddress}\",timestamp:\"${wallet.timestamp}\"}\n  // ...will add more stuff later, like signer or send/sign or balance\n}\nNft {\n  chainTicker: string // ticker of the chain, like eth, avax, sol, etc in lowercase\n  timestamp: number // in seconds, needed to mitigate multiple users using the same signature\n  address: string // address of the NFT contract\n  id: string // tokenId or index of the specific NFT used, must be string type, not number\n  signature: Signature // proof that author.address owns the nft\n  // how to resolve and verify NFT signatures https://github.com/bitsocialnet/evm-contract-challenge/blob/master/docs/nft.md\n}\nSignature {\n  signature: string // data in base64\n  publicKey: string // 32 bytes base64 string\n  type: 'ed25519' | 'eip191' // multiple versions/types to allow signing with metamask/other wallet or to change the signature fields or algorithm\n  signedPropertyNames: string[] // the fields that were signed as part of the signature e.g. ['title', 'content', 'author', etc.] client should require that certain fields be signed or reject the publication, e.g. 'content', 'author', 'timestamp' are essential\n}\nSigner {\n  privateKey?: string // 32 bytes base64 string\n  type: 'ed25519' // eip191 is only used for wallet/NFT signatures, not for Signer instances\n  publicKey?: string // 32 bytes base64 string\n  address: string // public key hash, not needed for signing\n  ipfsKey?: IpfsKey // a Key object used for importing into IpfsHttpClient https://docs.ipfs.io/reference/cli/#ipfs-key-import\n}\nCommunity /* (IPNS record, identified by community's public key) */ {\n  address: string // instance-only convenience property, not part of the IPNS record. Validate community address in signature to prevent a crypto domain resolving to an impersonated community\n  title?: string\n  description?: string\n  roles?: {[authorAddress: string]: CommunityRole} // each author address can be mapped to 1 CommunityRole\n  pubsubTopic?: string // the string to publish to in the pubsub, a public key of the community owner's choice\n  lastPostCid?: string // the most recent post in the linked list of posts\n  lastCommentCid?: string // the most recent comment (posts and replies included), last comment is often displayed with a list of forums\n  posts?: Pages // only preload page 1 sorted by 'hot', might preload more later, comments should include Comment + CommentUpdate data\n  statsCid?: string\n  createdAt: number\n  updatedAt: number\n  features?: CommunityFeatures\n  suggested?: CommunitySuggested\n  rules?: string[]\n  flairs?: {[key: 'post' | 'author']: Flair[]} // list of post/author flairs authors and mods can choose from\n  protocolVersion: '1.0.0' // semantic version of the protocol https://semver.org/\n  encryption: CommunityEncryption\n  signature: Signature // signature of the Community update by the community owner to protect against malicious gateway\n}\nCommunitySuggested { // values suggested by the community owner, the client/user can ignore them without breaking interoperability\n  primaryColor?: string\n  secondaryColor?: string\n  avatarUrl?: string\n  bannerUrl?: string\n  backgroundUrl?: string\n  language?: string\n  // TODO: menu links, wiki pages, sidebar widgets\n}\nCommunityFeatures { // any boolean that changes the functionality of the community, add \"no\" in front if doesn't default to false\n  // implemented\n  noUpvotes?: boolean\n  noPostUpvotes?: boolean\n  noReplyUpvotes?: boolean\n  noDownvotes?: boolean\n  noPostDownvotes?: boolean\n  noReplyDownvotes?: boolean\n  requirePostLink?: boolean // require post.link be defined and a valid https url\n  requirePostLinkIsMedia?: boolean // require post.link be media, e.g. for imageboards\n  requireReplyLink?: boolean // require reply.link be defined and a valid https url\n  requireReplyLinkIsMedia?: boolean // require reply.link be media\n  noMarkdownImages?: boolean // don't embed images in text posts markdown\n  noMarkdownVideos?: boolean // don't embed videos in text posts markdown\n  noMarkdownAudio?: boolean // don't embed audio in text posts markdown\n  noVideos?: boolean // block all comments with video links\n  noImages?: boolean // block all comments with image links\n  noAudio?: boolean // block all comments with audio links\n  noSpoilers?: boolean // author can't set spoiler = true on any comment\n  noVideoReplies?: boolean // block only replies with video links\n  noImageReplies?: boolean // block only replies with image links\n  noAudioReplies?: boolean // block only replies with audio links\n  noSpoilerReplies?: boolean // author can't set spoiler = true on replies\n  noNestedReplies?: boolean // no nested replies, like old school forums and 4chan. Maximum depth is 1\n  safeForWork?: boolean // informational flag indicating this community is safe for work\n  pseudonymityMode?: 'per-post' | 'per-reply' | 'per-author'\n  authorFlairs?: boolean // authors can choose their own author flairs (otherwise only mods can)\n  requireAuthorFlairs?: boolean // force authors to choose an author flair before posting\n  postFlairs?: boolean // authors can choose their own post flairs (otherwise only mods can)\n  requirePostFlairs?: boolean // force authors to choose a post flair before posting\n  // not implemented\n  noPolls?: boolean\n  noCrossposts?: boolean\n  markdownImageReplies?: boolean\n  markdownVideoReplies?: boolean\n}\nCommunityEncryption {\n  type: 'ed25519-aes-gcm' // https://github.com/pkcprotocol/pkc-js/blob/master/docs/encryption.md\n  publicKey: string // 32 bytes base64 string\n}\nCommunityRole {\n  role: 'owner' | 'admin' | 'moderator'\n  // TODO: custom roles with other props\n}\nFlair {\n  text: string\n  backgroundColor?: string\n  textColor?: string\n  expiresAt?: number // timestamp in seconds, a flair assigned to an author by a mod will follow the author in future comments, unless it expires\n}\nPages {\n  pages: {[key: PostsSortType | RepliesSortType]: Page} // e.g. community.posts.pages.hot.comments[0].cid = '12D3KooW...'\n  pageCids: {[key: PostsSortType | RepliesSortType]: pageCid} // e.g. community.posts.pageCids.topAll = '12D3KooW...'\n}\nPage {\n  nextCid: string // get next page (sorted by the same sort type)\n  comments: Comment[] // Comments should include merged Comment and CommentUpdate\n}\nPageIpfs /* (IPFS file) */ {\n  nextCid: string // get next page (sorted by the same sort type)\n  comments: PageIpfsComment[] // PageIpfs is fetched from IPFS, then Comments and CommentUpdates are merged to create the Page instance\n}\nPageIpfsComment {\n  comment: Comment\n  commentUpdate: CommentUpdate\n}\nPostsSortType: 'hot' | 'new' | 'active' | 'topHour' | 'topDay' | 'topWeek' | 'topMonth' | 'topYear' | 'topAll'\nRepliesSortType: 'best' | 'new' | 'old' | 'newFlat' | 'oldFlat'\nCommunityStats {\n  hourActiveUserCount: number\n  dayActiveUserCount: number\n  weekActiveUserCount: number\n  monthActiveUserCount: number\n  yearActiveUserCount: number\n  allActiveUserCount: number\n  hourPostCount: number\n  dayPostCount: number\n  weekPostCount: number\n  monthPostCount: number\n  yearPostCount: number\n  allPostCount: number\n  hourReplyCount: number\n  dayReplyCount: number\n  weekReplyCount: number\n  monthReplyCount: number\n  yearReplyCount: number\n  allReplyCount: number\n}\nChallengeType {\n  type: 'image/png' | 'text/plain' | 'chain/\u003cchainTicker\u003e'\n  //...other properties for more complex types later, e.g. an array of whitelisted addresses, a token address, etc,\n}\nMultisub /* (IPNS record Multisub.address) (not yet implemented) */ {\n  title?: string\n  description?: string\n  communities: MultisubCommunity[]\n  createdAt: number\n  updatedAt: number\n  signature: Signature // signature of the Multisub update by the multisub owner to protect against malicious gateway\n}\nMultisubCommunity { // (not yet implemented) this metadata is set by the owner of the Multisub, not the owner of the community\n  address: Address\n  title?: string\n  description?: string\n  languages?: string[] // client can detect language and hide/show community based on it\n  locations?: string[] // client can detect location and hide/show community based on it\n  features?: string[] // client can detect user's SFW setting and hide/show community based on it\n  tags?: string[] // arbitrary keywords used for search\n}\nPKCDefaults { // fetched once when app first load, a dictionary of default settings (not yet implemented)\n  multisubAddresses: {[multisubName: string]: Address}\n  // PKC has 3 default multisubs\n  multisubAddresses.all: Address // the default communities to show at url pkc.bso/p/all\n  multisubAddresses.crypto: Address // the communities to show at url pkc.bso/p/crypto\n  multisubAddresses.search: Address // list of thousands of semi-curated communities to \"search\" for in the client (only search the Multisub metadata, don't load each community)\n}\n```\n\n### Pubsub message types\n\n```js\nPubsubMessage: {\n  type: 'CHALLENGEREQUEST' | 'CHALLENGE' | 'CHALLENGEANSWER' | 'CHALLENGEVERIFICATION'\n  challengeRequestId: Uint8Array // (byte string in cbor) // multihash of challengeRequestMessage.signature.publicKey, each challengeRequestMessage must use a new public key\n  timestamp: number // in seconds, needed because publication.timestamp is encrypted\n  signature: PubsubSignature // each challengeRequestMessage must use a new public key\n  protocolVersion: '1.0.0' // semantic version of the protocol https://semver.org/\n  userAgent: `/pkc-js:${require('./package.json').version}/` // client name and version using this standard https://en.bitcoin.it/wiki/BIP_0014#Proposal\n}\nChallengeRequestMessage extends PubsubMessage /* (sent by post author) */ {\n  acceptedChallengeTypes: string[] // list of challenge types the client can do, for example cli clients or old clients won't do all types\n  encrypted: Encrypted\n  /* ChallengeRequestMessage.encrypted.ciphertext decrypts to JSON {\n    comment?: Comment\n    vote?: Vote\n    commentEdit?: CommentEdit\n    commentModeration?: CommentModeration\n    communityEdit?: CommunityEdit\n    challengeAnswers?: string[] // some challenges might be included in community.challenges and can be pre-answered\n    challengeCommentCids?: string[] // some challenges could require including comment cids in other communities, like friendly community karma challenges\n  }\n  pkc-js should decrypt the encrypted fields when possible, and add `ChallengeRequestMessage.publication` property for convenience (not part of the broadcasted pubsub message) */\n}\nChallengeMessage extends PubsubMessage /* (sent by community owner) */ {\n  encrypted: Encrypted\n  /* ChallengeMessage.encrypted.ciphertext decrypts to JSON {\n    challenges: Challenge[]\n  }\n  pkc-js should decrypt the encrypted fields when possible, and add `ChallengeMessage.challenges` property for convenience (not part of the broadcasted pubsub message) */\n}\nChallengeAnswerMessage extends PubsubMessage /* (sent by post author) */ {\n  encrypted: Encrypted\n  /* ChallengeAnswerMessage.encrypted.ciphertext decrypts to JSON {\n    challengeAnswers: string[] // for example ['2+2=4', '1+7=8']\n  }\n  pkc-js should decrypt the encrypted fields when possible, and add `ChallengeAnswerMessage.challengeAnswers` property for convenience (not part of the broadcasted pubsub message) */\n}\nChallengeVerificationMessage extends PubsubMessage /* (sent by community owner) */ {\n  challengeSuccess: bool // true if the challenge was successfully completed by the requester\n  challengeErrors?: {[challengeIndex: string]: string} // challenge index =\u003e challenge error, tell the user which challenge failed and why\n  reason?: string // reason for failed verification, for example post content is too long. could also be used for successful verification that bypass the challenge, for example because an author has good history\n  encrypted: Encrypted\n  /* ChallengeVerificationMessage.encrypted.ciphertext decrypts to JSON {\n    comment?: Comment // must contain missing props from comment publication, like depth, postCid, etc\n    commentUpdate?: CommentUpdate // must contain commentUpdate.cid and commentUpdate.signature when publication is comment\n  }\n  pkc-js should decrypt the encrypted fields when possible, and add `ChallengeVerificationMessage.publication` property for convenience (not part of the broadcasted pubsub message) */\n}\nChallenge {\n  type: 'image/png' | 'text/plain' | 'chain/\u003cchainTicker\u003e' // tells the client how to display the challenge, start with implementing image and text only first\n  challenge: string // base64 or utf8 required to complete the challenge, could be html, png, etc.\n  caseInsensitive?: boolean // challenge answer capitalization is ignored, informational only option added by the challenge file\n}\nEncrypted {\n  // examples available at https://github.com/pkcprotocol/pkc-js/blob/master/docs/encryption.md\n  ciphertext: Uint8Array // (byte string in cbor) encrypted byte string with AES GCM 128 // https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Galois/counter_(GCM)\n  iv: Uint8Array // (byte string in cbor) iv for the AES GCM 128 encrypted content\n  tag: Uint8Array // (byte string in cbor) authentication tag, AES GCM has authentication tag https://en.wikipedia.org/wiki/Galois/Counter_Mode\n  type: 'ed25519-aes-gcm'\n}\nPubsubSignature {\n  signature: Uint8Array // (byte string in cbor)\n  publicKey: Uint8Array // (byte string in cbor) 32 bytes\n  type: 'ed25519' | 'eip191' // multiple versions/types to allow signing with metamask/other wallet or to change the signature fields or algorithm\n  signedPropertyNames: string[] // the fields that were signed as part of the signature e.g. ['title', 'content', 'author', etc.] client should require that certain fields be signed or reject the publication, e.g. 'content', 'author', 'timestamp' are essential\n}\n```\n\n### Libraries that use pkc-js\n\n- [bitsocial-cli](https://github.com/bitsocialnet/bitsocial-cli) - CLI client for the Bitsocial protocol\n- [bitsocial-react-hooks](https://github.com/bitsocialnet/bitsocial-react-hooks) - React hooks for building Bitsocial protocol UIs\n- [bso-resolver](https://github.com/bitsocialnet/bso-resolver) - Resolves .bso human readable names to PKC cryptographic identities\n\n# API\n\n- [PKC API](#pkc-api)\n  - [`PKC(pkcOptions)`](#pkcpkcoptions)\n  - [`pkc.getMultisub(multisubAddress)`](#pkcgetmultisubmultisubaddress) *(not yet implemented)*\n  - [`pkc.getCommunity({address})`](#pkcgetcommunityaddress)\n  - [`pkc.getComment({cid})`](#pkcgetcommentcid)\n  - [`pkc.createMultisub(createMultisubOptions)`](#pkccreatemultisubcreatemultisuboptions) *(not yet implemented)*\n  - [`pkc.createCommunity(createCommunityOptions)`](#pkccreatecommunitycreatecommunityoptions)\n  - [`pkc.createCommunityEdit(createCommunityEditOptions)`](#pkccreatecommunityeditcreatecommunityeditoptions)\n  - [`pkc.createComment(createCommentOptions)`](#pkccreatecommentcreatecommentoptions)\n  - [`pkc.createCommentEdit(createCommentEditOptions)`](#pkccreatecommenteditcreatecommenteditoptions)\n  - [`pkc.createCommentModeration(createCommentModerationOptions)`](#pkccreatecommentmoderationcreatecommentmoderationoptions)\n  - [`pkc.createVote(createVoteOptions)`](#pkccreatevotecreatevoteoptions)\n  - [`pkc.createSigner(createSignerOptions)`](#pkccreatesignercreatesigneroptions)\n  - `pkc.communities`\n  - `pkc.clients`\n  - [`pkc.getDefaults()`](#pkcgetdefaults) *(not yet implemented)*\n  - `pkc.fetchCid({cid})`\n  - `pkc.resolveAuthorAddress({address})`\n  - `PKC.getShortAddress({address})`\n  - `PKC.getShortCid({cid})`\n  - `PKC.setNativeFunctions(nativeFunctions)`\n  - `PKC.nativeFunctions`\n  - `PKC.challenges`\n- [PKC Events](#pkc-events)\n  - [`communitieschange`](#communitieschange)\n  - `error`\n- [Community API](#community-api)\n  - [`community.edit(communityEditOptions)`](#communityeditcommunityeditoptions)\n  - [`community.start()`](#communitystart)\n  - [`community.stop()`](#communitystop)\n  - [`community.update()`](#communityupdate)\n  - `community.delete()`\n  - `community.address`\n  - `community.shortAddress`\n  - `community.roles`\n  - `community.posts`\n  - `community.lastPostCid`\n  - `community.pubsubTopic`\n  - `community.rules`\n  - `community.flairs`\n  - `community.suggested`\n  - `community.features`\n  - `community.settings`\n  - `community.createdAt`\n  - `community.updatedAt`\n  - `community.statsCid`\n  - `community.updateCid`\n  - `community.signer`\n  - `community.started`\n  - `community.state`\n  - `community.updatingState`\n  - `community.startedState`\n- [Community Events](#community-events)\n  - [`update`](#update)\n  - [`challengerequest`](#challengerequest)\n  - [`challengeanswer`](#challengeanswer)\n  - `challenge`\n  - `challengeverification`\n  - `error`\n  - [`statechange`](#statechange)\n  - [`updatingstatechange`](#updatingstatechange)\n  - [`startedstatechange`](#startedstatechange)\n- [Comment API](#comment-api)\n  - [`comment.publish()`](#commentpublish)\n  - [`comment.publishChallengeAnswers()`](#commentpublishchallengeanswerschallengeanswers)\n  - [`comment.update()`](#commentupdate)\n  - [`comment.stop()`](#commentstop)\n  - `comment.author`\n  - `comment.timestamp`\n  - `comment.signature`\n  - `comment.previousCid`\n  - `comment.postCid`\n  - `comment.parentCid`\n  - `comment.communityAddress`\n  - `comment.shortCommunityAddress`\n  - `comment.title`\n  - `comment.content`\n  - `comment.link`\n  - `comment.linkWidth`\n  - `comment.linkHeight`\n  - `comment.thumbnailUrl`\n  - `comment.thumbnailUrlWidth`\n  - `comment.thumbnailUrlHeight`\n  - `comment.flairs`\n  - `comment.spoiler`\n  - `comment.depth`\n  - `comment.state`\n  - `comment.updatingState`\n  - `comment.publishingState`\n  - `(only available after challengeverification event)`\n  - `comment.cid`\n  - `comment.shortCid`\n  - `(only available after first update event)`\n  - `comment.edit`\n  - `comment.original`\n  - `comment.upvoteCount`\n  - `comment.downvoteCount`\n  - `comment.updatedAt`\n  - `comment.pinned`\n  - `comment.deleted`\n  - `comment.removed`\n  - `comment.locked`\n  - `comment.reason`\n  - `comment.replies`\n  - `comment.replyCount`\n- [Comment Events](#comment-events)\n  - [`update`](#update)\n  - [`challenge`](#challenge)\n  - [`challengeverification`](#challengeverification)\n  - `challengerequest`\n  - `challengeanswer`\n  - `error`\n  - [`statechange`](#statechange-1)\n  - [`updatingstatechange`](#updatingstatechange-1)\n  - [`publishingstatechange`](#publishingstatechange)\n- [Pages API](#pages-api)\n  - [`pages.getPage({cid})`](#pagesgetpagecid)\n  - `pages.pages`\n  - `pages.pageCids`\n- Client API\n  - `client.state`\n  - `client.settings`\n  - `client.setSettings(pkcRpcSettings)`\n  - `client.rpcCall(method, params)`\n  - `client.getPeers()`\n  - `client.getStats()`\n- [Client Events](#client-events)\n  - [`statechange`](#statechange-2)\n  - [`settingschange`](#settingschange)\n\n## PKC API\nThe PKC API for reading and writing to and from communities.\n\n### `PKC(pkcOptions)`\n\n\u003e Create a PKC instance.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| pkcOptions | `PKCOptions` | Options for the PKC instance |\n\n##### PKCOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| ipfsGatewayUrls | `string[]` or `undefined` | `['https://ipfsgateway.xyz', 'https://gateway.plebpubsub.xyz', 'https://gateway.forumindex.com']` | Optional URLs of IPFS gateways |\n| kuboRpcClientsOptions | `(string \\| KuboRpcClientOptions)[]` or `undefined` | `undefined` | Optional URLs of Kubo IPFS APIs or [KuboRpcClientOptions](https://www.npmjs.com/package/kubo-rpc-client#options). Use `'http://localhost:5001/api/v0'` to point at a local Kubo node. Cannot be combined with `libp2pJsClientsOptions`. |\n| pubsubKuboRpcClientsOptions | `(string \\| KuboRpcClientOptions)[]` or `undefined` | `[{url: 'https://pubsubprovider.xyz/api/v0'}, {url: 'https://plebpubsub.xyz/api/v0'}]` | Optional URLs or [KuboRpcClientOptions](https://www.npmjs.com/package/kubo-rpc-client#options) used for pubsub publishing when `kuboRpcClientsOptions` isn't available, like in the browser |\n| pkcRpcClientsOptions | `string[]` or `undefined` | `undefined` | Optional websocket URLs of PKC RPC servers, required to run a community from a browser/electron/webview |\n| httpRoutersOptions | `string[]` or `undefined` | `['https://peers.pleb.bot', 'https://routing.lol', 'https://peers.forumindex.com', 'https://peers.plebpubsub.xyz']` | URLs of HTTP delegated-routing endpoints used by `libp2pJsClientsOptions` for content/peer routing and IPNS lookups. Each URL must start with `http://` or `https://`. |\n| libp2pJsClientsOptions | `Array\u003c{key: string, libp2pOptions?: Partial\u003cLibp2pOptions\u003e, heliaOptions?: Partial\u003cHeliaOptions\u003e}\u003e` or `undefined` | `undefined` | Optional in-process libp2p/Helia client. When set, replaces `kuboRpcClientsOptions` and `pubsubKuboRpcClientsOptions` (which are then ignored). At most one entry; `key` is a unique identifier. `libp2pOptions` is forwarded to [Helia's libp2p](https://github.com/ipfs/helia) (e.g. `connectionGater`, `services`, transports), `heliaOptions` to [`createHelia`](https://github.com/ipfs/helia) (e.g. `blockstore`, `blockBrokers`). Requires `httpRoutersOptions` to be set. |\n| dataPath | `string` or `undefined` | `.pkc` folder in the current working directory | (Node only) Optional folder path to create/resume the user and community databases |\n| resolveAuthorNames | `boolean` or `undefined` | `true` | Optionally disable resolving crypto domain author names, which can be done lazily later to save time |\n| nameResolvers | `NameResolver[]` or `undefined` | `undefined` | Custom resolvers for crypto domain names. Each resolver: `{key, resolve, canResolve, provider, destroy?}`. |\n| validatePages | `boolean` or `undefined` | `true` | If `false`, pages returned in `commentUpdate` / `Community` / `getPage` are not validated. Use only when you trust the source. |\n| userAgent | `string` or `undefined` | `/pkc-js:\u003cversion\u003e/` | User-agent string sent on outgoing HTTP requests. |\n| challenges | `Record\u003cstring, ChallengeFileFactory\u003e` or `undefined` | `undefined` | Instance-level challenge registry; keys shadow built-in challenges with the same name. See [docs/protocol/challenge-flow.md](docs/protocol/challenge-flow.md). |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cPKC\u003e` | A `PKC` instance |\n\n#### Example\n\n```js\nconst PKC = require('@pkcprotocol/pkc-js')\nconst options = {\n  kuboRpcClientsOptions: ['http://localhost:5001/api/v0'], // optional, must run an IPFS node to use localhost:5001/api/v0\n  dataPath: __dirname\n}\nconst pkc = await PKC(options) // should be independent instance, not singleton\npkc.on('error', console.log)\n```\n\n### `pkc.getMultisub(multisubAddress)` *(not yet implemented)*\n\n\u003e Get a multisub by its `Address`. A multisub is a list of communities curated by the creator of the multisub. E.g. `'pkc.bso/#/m/john.bso'` would display a feed of the multisub communities curated by `'john.bso'` (multisub `Address` `'john.bso'`).\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| multisubAddress | `string` | The `Address` of the multisub |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cMultisub\u003e` | A `Multisub` instance. |\n\n#### Example\n\n```js\nconst multisubAddress = '12D3KooW...' // or 'john.bso'\nconst multisub = await pkc.getCommunity({address: multisubAddress})\nconst multisubCommunityAddresses = multisub.map(community =\u003e community.address)\nconsole.log(multisubCommunityAddresses)\n```\n\n### `pkc.getCommunity({address})`\n\n\u003e Get a community by its `Address`.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| address | `string` | The `Address` of the community |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cCommunity\u003e` | A `Community` instance. |\n\n#### Example\n\n```js\nconst communityAddress = '12D3KooW...'\nconst community = await pkc.getCommunity({address: communityAddress})\nconsole.log(community)\n\nlet currentPostCid = community.lastPostCid\nconst scrollAllCommunityPosts = async () =\u003e {\n  while (currentPostCid) {\n    const post = await pkc.getComment({cid: currentPostCid})\n    console.log(post)\n    currentPostCid = post.previousCid\n  }\n  console.log('there are no more posts')\n}\nscrollAllCommunityPosts()\n/*\nPrints:\n{ ...TODO }\n*/\n```\n\n### `pkc.getComment({cid})`\n\n\u003e Get a PKC comment by its IPFS CID. Posts are also comments.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | `string` | The IPFS CID of the comment |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cComment\u003e` | A `Comment` instance |\n\n#### Example\n\n```js\nconst commentCid = 'Qm...'\nconst comment = await pkc.getComment({cid: commentCid})\nconsole.log('comment:', comment)\ncomment.on('update', updatedComment =\u003e console.log('comment with latest data', updatedComment))\nif (comment.parentCid) { // comment with no parent cid is a post\n  pkc.getComment({cid: comment.parentCid}).then(parentPost =\u003e console.log('parent post:', parentPost))\n}\npkc.getCommunity({address: comment.communityAddress}).then(community =\u003e console.log('community:', community))\npkc.getComment({cid: comment.previousCid}).then(previousComment =\u003e console.log('previous comment:', previousComment))\n/*\nPrints:\n{ ...TODO }\n*/\n```\n\n### `pkc.createMultisub(createMultisubOptions)` *(not yet implemented)*\n\n\u003e Create a multisub instance. A multisub is a list of communities curated by the creator of the multisub. E.g. `'pkc.bso/#/m/john.bso'` would display a feed of the multisub communities curated by `'john.bso'` (multisub `Address` `'john.bso'`).\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| createMultisubOptions | `CreateMultisubOptions` | Options for the `Multisub` instance |\n\n##### CreateMultisubOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| address | `string` or `undefined` | `Address` of the multisub |\n| signer | `Signer` or `undefined` | (Multisub owners only) Optional `Signer` of the community to create a multisub with a specific private key |\n| title | `string` or `undefined` | Title of the multisub |\n| description | `string` or `undefined` | Description of the multisub |\n| communities | `MultisubCommunity[]` or `undefined` | List of `MultisubCommunity` of the multisub |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cMultisub\u003e` | A `Multisub` instance |\n\n#### Example\n\n```js\nconst multisubOptions = {signer}\nconst multisub = await pkc.createMultisub(multisubOptions)\n\n// edit the multisub info in the database (only in Node and if multisub.signer is defined)\nawait multisub.edit({\n  address: 'funny-communities.bso',\n  title: 'Funny communities',\n  description: 'The funniest communities',\n})\n\n// add a list of communities to the multisub in the database (only in Node and if multisub.signer is defined)\nconst multisubCommunity1 = {address: 'funny.bso', title: 'Funny things', tags: ['funny']}\nconst multisubCommunity2 = {address: 'even-more-funny.bso'}\nawait multisub.edit({communities: [multisubCommunity1, multisubCommunity2]})\n\n// start publishing updates to your multisub (only in Node and if multisub.signer is defined)\nawait multisub.start()\n\n// stop publishing updates to your multisub\nawait multisub.stop()\n```\n\n### `pkc.createCommunity(createCommunityOptions)`\n\n\u003e Create a community instance. Should update itself on update events after `Community.update()` is called if `CreateCommunityOptions.address` exists. If the community database corresponding to `community.address` exists locally, can call `Community.edit(communityEditOptions)` to edit the community as the owner, and `Community.start()` to listen for new posts on the pubsub and publish updates as the owner.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| createCommunityOptions | `CreateCommunityOptions` | Options for the `Community` instance |\n\n##### CreateCommunityOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Default | Description |\n| ---- | ---- | ------- | ----------- |\n| address | `string` or `undefined` | `undefined` | `Address` of the community |\n| signer | `Signer` or `undefined` | `undefined` | (Community owners only) Optional `Signer` of the community to create a community with a specific private key |\n| ...community | `any` | `undefined` | `CreateCommunityOptions` can also initialize any property on the `Community` instance |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cCommunity\u003e` | A `Community` instance |\n\n#### Example\n\n```js\nconst PKC = require('@pkcprotocol/pkc-js')\nconst pkcOptions = {\n  kuboRpcClientsOptions: ['http://localhost:5001/api/v0'], // optional, must run an IPFS node to use localhost:5001/api/v0\n  dataPath: __dirname\n}\nconst pkc = await PKC(pkcOptions)\npkc.on('error', console.log)\n\n// create a new local community as the owner\nconst community = await pkc.createCommunity()\n\n// create a new local community as the owner, already with settings\nconst community = await pkc.createCommunity({title: 'Memes', description: 'Post your memes here.'})\n\n// create a new local community as the owner with a premade signer\nconst signer = await pkc.createSigner()\nconst community = await pkc.createCommunity({signer})\n// signer.address === community.address\n\n// create a new local community as the owner with a premade signer, already with settings\nconst signer = await pkc.createSigner()\nconst community = await pkc.createCommunity({signer, title: 'Memes', description: 'Post your memes here.'})\n\n// instantiate an already existing community instance\nconst communityOptions = {address: '12D3KooW...',}\nconst community = await pkc.createCommunity(communityOptions)\n\n// edit the community info in the database\nawait community.edit({\n  title: 'Memes',\n  description: 'Post your memes here.',\n  pubsubTopic: '12D3KooW...'\n})\n\n// start publishing updates every 5 minutes\nawait community.start()\n\n// instantiate an already existing community instance and initialize any property on it\nconst community = await pkc.createCommunity({\n  address: '12D3KooW...',\n  title: 'Memes',\n  posts: {\n    pages: {\n      hot: {\n        nextCid: 'Qm...',\n        comments: [{content: 'My first post', ...post}]\n      }\n    },\n    pageCids: {topAll: 'Qm...', new: 'Qm...', ...pageCids}\n  }\n})\nconsole.log(community.title) // prints 'Memes'\nconsole.log(community.posts.pages.hot.comments[0].content) // prints 'My first post'\n```\n\n### `pkc.createCommunityEdit(createCommunityEditOptions)`\n\n\u003e Create a `CommunityEdit` instance, which can be used by admins to edit a community remotely over pubsub. A `CommunityEdit` is a regular `Publication` and must still be published and go through a challenge handshake.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| createCommunityEditOptions | `CreateCommunityEditOptions` | The community edit to create, extends [`CommunityEditOptions`](#communityeditoptions) |\n\n##### CreateCommunityEditOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| communityAddress | `string` or `undefined` | `Address` of the community (derived from `communityName` or `communityPublicKey` if not provided). Providing both `communityPublicKey` and `communityName` is recommended for speed |\n| communityPublicKey | `string` or `undefined` | IPNS public key of the community (can identify the community instead of `communityAddress`) |\n| communityName | `string` or `undefined` | Domain name of the community, e.g. `'memes.bso'` (can identify the community instead of `communityAddress`) |\n| timestamp | `number` or `undefined` | Time of publishing in seconds, `Math.round(Date.now() / 1000)` if undefined |\n| author | `Author` | `author.address` of the community edit must have `community.roles` `'admin'` |\n| signer | `Signer` | Signer of the community edit |\n| communityEdit | `CommunityEditOptions` | The community edit options, see [`CommunityEditOptions`](#communityeditoptions) |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cCommunityEdit\u003e` | A `CommunityEdit` instance |\n\n#### Example\n\n```js\nconst createCommunityEditOptions = {communityName: 'news.bso', communityEdit: {title: 'New title'}}\nconst communityEdit = await pkc.createCommunityEdit(createCommunityEditOptions)\ncommunityEdit.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  communityEdit.publishChallengeAnswers(challengeAnswers)\n})\ncommunityEdit.on('challengeverification', console.log)\nawait communityEdit.publish()\n```\n\n### `pkc.createComment(createCommentOptions)`\n\n\u003e Create a `Comment` instance. Posts/Replies are also comments. Should update itself on update events after `Comment.update()` is called if `CreateCommentOptions.cid` exists.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| createCommentOptions | `CreateCommentOptions` | The comment to create |\n\n##### CreateCommentOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| communityAddress | `string` or `undefined` | `Address` of the community (derived from `communityName` or `communityPublicKey` if not provided). Providing both `communityPublicKey` and `communityName` is recommended for speed |\n| communityPublicKey | `string` or `undefined` | IPNS public key of the community (can identify the community instead of `communityAddress`) |\n| communityName | `string` or `undefined` | Domain name of the community, e.g. `'memes.bso'` (can identify the community instead of `communityAddress`) |\n| timestamp | `number` or `undefined` | Time of publishing in seconds, `Math.round(Date.now() / 1000)` if undefined |\n| author | `Author` | Author of the comment |\n| signer | `Signer` | Signer of the comment |\n| parentCid | `string` or `undefined` | The parent comment CID, undefined if comment is a post, same as postCid if comment is top level |\n| content | `string` or `undefined` | Content of the comment, link posts have no content |\n| title | `string` or `undefined` | If comment is a post, it needs a title |\n| link | `string` or `undefined` | If comment is a post, it might be a link post |\n| spoiler | `boolean` or `undefined` | Hide the comment thumbnail behind spoiler warning |\n| flairs | `Flair[]` or `undefined` | Author or mod chosen colored labels for the comment |\n| challengeRequest | `ChallengeRequest` or `undefined` | Optional properties to pass to `ChallengeRequestPubsubMessage` |\n| cid | `string` or `undefined` | (Not for publishing) Gives access to `Comment.on('update')` for a comment already fetched |\n| ...comment | `any` | `CreateCommentOptions` can also initialize any property on the `Comment` instance |\n\n##### ChallengeRequest\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| challengeAnswers | `string[]` or `undefined` | Optional pre-answers to community.challenges |\n| challengeCommentCids | `string[]` or `undefined` | Optional comment cids for community.challenges related to author karma/age in other communities |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cComment\u003e` | A `Comment` instance |\n\n#### Example\n\n```js\nconst comment = await pkc.createComment(createCommentOptions)\ncomment.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  comment.publishChallengeAnswers(challengeAnswers)\n})\ncomment.on('challengeverification', console.log)\nawait comment.publish()\n\n// initialize any property on the Comment instance\nconst comment = await pkc.createComment({\n  cid: 'Qm...',\n  content: 'My first post',\n  locked: true,\n  upvoteCount: 100,\n  replies: {\n    pages: {\n      best: {\n        nextCid: 'Qm...',\n        comments: [{content: 'My first reply', ...reply}]\n      }\n    },\n    pageCids: {new: 'Qm...', old: 'Qm...', ...pageCids}\n  }\n})\nconsole.log(comment.content) // prints 'My first post'\nconsole.log(comment.locked) // prints true\nconsole.log(comment.upvoteCount) // prints 100\nconsole.log(comment.replies.pages.best.comments[0].content) // prints 'My first reply'\n```\n\n### `pkc.createCommentEdit(createCommentEditOptions)`\n\n\u003e Create a `CommentEdit` instance, which can be used by authors to edit their own comments. A `CommentEdit` must still be published and go through a challenge handshake.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| createCommentEditOptions | `CreateCommentEditOptions` | The comment edit to create |\n\n##### CreateCommentEditOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| communityAddress | `string` or `undefined` | `Address` of the community (derived from `communityName` or `communityPublicKey` if not provided). Providing both `communityPublicKey` and `communityName` is recommended for speed |\n| communityPublicKey | `string` or `undefined` | IPNS public key of the community (can identify the community instead of `communityAddress`) |\n| communityName | `string` or `undefined` | Domain name of the community, e.g. `'memes.bso'` (can identify the community instead of `communityAddress`) |\n| commentCid | `string` | The comment CID to be edited (don't use 'cid' because eventually CommentEdit.cid will exist) |\n| timestamp | `number` or `undefined` | Time of publishing in seconds, `Math.round(Date.now() / 1000)` if undefined |\n| author | `Author` | Author of the `CommentEdit` publication, must be original author. Not used to edit the `comment.author` property, only to authenticate the `CommentEdit` publication |\n| signer | `Signer` | Signer of the edit, must be original author |\n| content | `string` or `undefined` | Edited content of the comment |\n| deleted | `boolean` or `undefined` | Edited deleted status of the comment |\n| flairs | `Flair[]` or `undefined` | Edited flairs of the comment |\n| spoiler | `boolean` or `undefined` | Edited spoiler of the comment |\n| reason | `string` or `undefined` | Reason of the edit |\n| challengeRequest | `ChallengeRequest` or `undefined` | Optional properties to pass to `ChallengeRequestPubsubMessage` |\n\n##### ChallengeRequest\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| challengeAnswers | `string[]` or `undefined` | Optional pre-answers to community.challenges |\n| challengeCommentCids | `string[]` or `undefined` | Optional comment cids for community.challenges related to author karma/age in other communities |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cCommentEdit\u003e` | A `CommentEdit` instance |\n\n#### Example\n\n```js\nconst commentEdit = await pkc.createCommentEdit(createCommentEditOptions)\ncommentEdit.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  commentEdit.publishChallengeAnswers(challengeAnswers)\n})\ncommentEdit.on('challengeverification', console.log)\nawait commentEdit.publish()\n```\n\n### `pkc.createCommentModeration(createCommentModerationOptions)`\n\n\u003e Create a `CommentModeration` instance, which can be used by moderators to remove comments. A `CommentModeration` must still be published and go through a challenge handshake.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| createCommentModerationOptions | `CreateCommentModerationOptions` | The comment moderation to create |\n\n##### CreateCommentModerationOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| communityAddress | `string` or `undefined` | `Address` of the community (derived from `communityName` or `communityPublicKey` if not provided). Providing both `communityPublicKey` and `communityName` is recommended for speed |\n| communityPublicKey | `string` or `undefined` | IPNS public key of the community (can identify the community instead of `communityAddress`) |\n| communityName | `string` or `undefined` | Domain name of the community, e.g. `'memes.bso'` (can identify the community instead of `communityAddress`) |\n| commentCid | `string` | The comment CID to be edited (don't use 'cid' because eventually CommentEdit.cid will exist) |\n| timestamp | `number` or `undefined` | Time of publishing in seconds, `Math.round(Date.now() / 1000)` if undefined |\n| author | `Author` | Author of the `CommentModeration` publication, must be moderator. Not used to edit the `comment.author` property, only to authenticate the `CommentModeration` publication |\n| signer | `Signer` | Signer of the edit, must be moderator |\n| commentModeration | `CommentModerationOptions` | The comment moderation options |\n| challengeRequest | `ChallengeRequest` or `undefined` | Optional properties to pass to `ChallengeRequestPubsubMessage` |\n\n##### CommentModerationOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| flairs | `Flair[]` or `undefined` | Edited flairs of the comment |\n| spoiler | `boolean` or `undefined` | Edited spoiler of the comment |\n| nsfw | `boolean` or `undefined` | Edited nsfw status of the comment |\n| reason | `string` or `undefined` | Reason of the edit |\n| pinned | `boolean` or `undefined` | Edited pinned status of the comment |\n| locked | `boolean` or `undefined` | Edited locked status of the comment |\n| archived | `boolean` or `undefined` | Edited archived status of the comment |\n| approved | `boolean` or `undefined` | Approving a comment that's pending approval |\n| removed | `boolean` or `undefined` | Edited removed status of the comment |\n| purged | `boolean` or `undefined` | Purged status of the comment |\n| author | `CommentModerationAuthorOptions` or `undefined` | Edited author property of the comment |\n\n##### CommentModerationAuthorOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| banExpiresAt | `number` or `undefined` | Comment author was banned for this comment |\n| flairs | `Flair[]` or `undefined` | Edited flairs of the comment author |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cCommentModeration\u003e` | A `CommentModeration` instance |\n\n#### Example\n\n```js\nconst commentModeration = await pkc.createCommentModeration(createCommentModerationOptions)\ncommentModeration.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  commentModeration.publishChallengeAnswers(challengeAnswers)\n})\ncommentModeration.on('challengeverification', console.log)\nawait commentModeration.publish()\n```\n\n### `pkc.createVote(createVoteOptions)`\n\n\u003e Create a `Vote` instance. `Vote` inherits from `Publication`, like `Comment`, so has the same API.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| createVoteOptions | `CreateVoteOptions` | The vote to create |\n\n##### CreateVoteOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| communityAddress | `string` or `undefined` | `Address` of the community (derived from `communityName` or `communityPublicKey` if not provided). Providing both `communityPublicKey` and `communityName` is recommended for speed |\n| communityPublicKey | `string` or `undefined` | IPNS public key of the community (can identify the community instead of `communityAddress`) |\n| communityName | `string` or `undefined` | Domain name of the community, e.g. `'memes.bso'` (can identify the community instead of `communityAddress`) |\n| commentCid | `string` | The comment or post to vote on |\n| timestamp | `number` or `undefined` | Time of publishing in seconds, `Math.round(Date.now() / 1000)` if undefined |\n| author | `Author` | Author of the comment, will be needed for voting with NFTs or tokens |\n| vote | `1` or `0` or `-1` | 0 is for resetting a vote |\n| signer | `Signer` | Signer of the vote |\n| challengeRequest | `ChallengeRequest` or `undefined` | Optional properties to pass to `ChallengeRequestPubsubMessage` |\n\n##### ChallengeRequest\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| challengeAnswers | `string[]` or `undefined` | Optional pre-answers to community.challenges |\n| challengeCommentCids | `string[]` or `undefined` | Optional comment cids for community.challenges related to author karma/age in other communities |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cVote\u003e` | A `Vote` instance |\n\n#### Example\n\n```js\nconst vote = await pkc.createVote(createVoteOptions)\nvote.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  vote.publishChallengeAnswers(challengeAnswers)\n})\nvote.on('challengeverification', console.log)\nawait vote.publish()\n```\n\n### `pkc.createSigner(createSignerOptions)`\n\n\u003e Create a `Signer` instance to be used in `CreateCommentOptions`.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| createSignerOptions | `CreateSignerOptions` or `undefined` | The options of the signer |\n\n##### CreateSignerOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| privateKey | `string` or `undefined` | If undefined, generate a random `privateKey` |\n| type | `string` | Required if `privateKey` defined, only `'ed25519'` for now |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cSigner\u003e` | A `Signer` instance |\n\n#### Example\n\n```js\nconst newRandomSigner = await pkc.createSigner()\nconst signerFromPrivateKey = await pkc.createSigner({privateKey: 'AbCd...', type: 'ed25519'})\n```\n\n### `pkc.communities`\n\n\u003e A `string[]` of community addresses stored locally. Updates when communities are created or deleted. Listen for changes with the `communitieschange` event.\n\n#### Example\n\n```js\n// start all the communities you own and have stored locally\nfor (const address of pkc.communities) {\n  const community = await pkc.createCommunity({address})\n  await community.start()\n}\n```\n\n### `pkc.getDefaults()` *(not yet implemented)*\n\n\u003e Get the default global PKC settings, e.g. the default multisubs like p/all, p/dao, etc.\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cPKCDefaults\u003e` | A `PKCDefaults` instance. |\n\n#### Example\n\n```js\nconst pkcDefaults = await pkc.getDefaults()\nconst allMultisub = await pkc.getMultisub(pkcDefaults.multisubAddresses.all)\nconst allCommunityAddresses = allMultisub.map(community =\u003e community.address)\nconsole.log(allCommunityAddresses)\n```\n\n## PKC Events\nThe PKC events.\n\n### `communitieschange`\n\n\u003e `PKC.communities` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `string[]` | The `PKC.communities` property |\n\n## Community API\nThe community API for getting community updates, or creating, editing, running a community as an owner.\n\n### `community.edit(communityEditOptions)`\n\n\u003e Edit the content/information of a community in your local database. Only usable if the community database corresponding to `community.address` exists locally  (ie. you are the community owner).\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| community | `CommunityEditOptions` | The content/information of the community |\n\n##### CommunityEditOptions\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| address | `string` or `undefined` | Address of the community, used to add a crypto domain |\n| signer | `Signer` or `undefined` | Signer of the community, useful to change the private if the owner gets hacked, but still has his crypto domain\n| title | `string` or `undefined` | Title of the community |\n| description | `string` or `undefined` | Description of the community |\n| roles | `{[authorAddress: string]: CommunityRole}` or `undefined` | Author addresses of the moderators |\n| lastPostCid | `string` or `undefined` | The most recent post in the linked list of posts |\n| posts | `Pages` or `undefined` | Only preload page 1 sorted by 'hot', might preload more later, should include some child comments and vote counts for each post |\n| pubsubTopic | `string` or `undefined` | The string to publish to in the pubsub, a public key of the community owner's choice |\n| features | `CommunityFeatures` or `undefined` | The features of the community |\n| suggested | `CommunitySuggested` or `undefined` | The suggested client settings for the community |\n| flairs | `{[key: 'post' or 'author']: Flair[]}` or `undefined` | The list of flairs (colored labels for comments or authors) authors or mods can choose from |\n| settings | `CommunitySettings` or `undefined` | The private community.settings property of the community, not shared in the community IPNS |\n\n##### CommunitySettings\n\nAn object which may have the following keys:\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| fetchThumbnailUrls | `boolean` or `undefined` | Fetch the thumbnail URLs of comments `comment.link` property, could reveal the IP address of the community node |\n| fetchThumbnailUrlsProxyUrl | `string` or `undefined` | The HTTP proxy URL used to fetch thumbnail URLs |\n\n#### Example\n\n```js\n// TODO\n```\n\n### `community.start()`\n\n\u003e Start listening for new posts on the pubsub, and publishing them every 5 minutes. Only usable if the community database corresponding to `community.address` exists locally  (ie. you are the community owner).\n\n#### Example\n\n```js\nconst options = {\n  title: 'Your community title'\n}\nconst community = await pkc.createCommunity(options)\n// edit the community info in the database\nawait community.edit({\n  title: 'Memes',\n  description: 'Post your memes here.',\n  pubsubTopic: '12D3KooW...'\n})\n// start publishing updates/new posts\nawait community.start()\n```\n\n### `community.stop()`\n\n\u003e Stop polling the network for new community updates started by community.update(). Also stop listening for new posts on the pubsub started by community.start(), and stop publishing them every 5 minutes.\n\n### `community.update()`\n\n\u003e Start polling the network for new posts published in the community, update itself and emit the 'update' event. Only usable if community.address exists.\n\n#### Example\n\n```js\nconst options = {\n  address: '12D3KooW...'\n}\nconst community = await pkc.createCommunity(options)\ncommunity.on('update', (updatedCommunityInstance) =\u003e {\n  console.log(updatedCommunityInstance)\n\n  // if you want to stop polling for new updates after only the first one\n  community.stop()\n})\ncommunity.update()\n```\n\n## Community Events\nThe community events.\n\n### `update`\n\n\u003e The community's IPNS record has been updated, which means new posts may have been published.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `Community` | The updated `Community` instance (the instance emits itself), i.e. `this` |\n\n#### Example\n\n```js\nconst options = {\n  address: '12D3KooW...'\n}\nconst community = await pkc.createCommunity(options)\ncommunity.on('update', (updatedCommunity) =\u003e console.log(updatedCommunity))\ncommunity.update()\n\n// stop updating in 10 minutes\nsetTimeout(() =\u003e community.stop(), 60000)\n```\n\n### `challengerequest`\n\n\u003e When the user publishes a comment, he makes a `'challengerequest'` to the pubsub, the community owner will send back a `challenge`, eg. a captcha that the user must complete.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `ChallengeRequestMessage` | The comment of the user and the challenge request |\n\nObject is of the form:\n\n```js\n{ // ...TODO }\n```\n\n#### Example\n\n```js\n{ // ...TODO }\n```\n\n### `challengeanswer`\n\n\u003e After receiving a `Challenge`, the user owner will send back a `challengeanswer`.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `ChallengeAnswerMessage` | The challenge answer |\n\nObject is of the form:\n\n```js\n{ // ...TODO }\n```\n\n### `statechange`\n\n\u003e `Community.state` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `'stopped' \\| 'updating' \\| 'started'` | The `Community.state` property |\n\n### `updatingstatechange`\n\n\u003e `Community.updatingState` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `'stopped' \\| 'resolving-address' \\| 'fetching-ipns' \\| 'fetching-ipfs' \\| 'failed' \\| 'succeeded'` | The `Community.updatingState` property |\n\n### `startedstatechange`\n\n\u003e `Community.startedState` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `'stopped' \\| 'fetching-ipns' \\| 'publishing-ipns' \\| 'failed' \\| 'succeeded'` | The `Community.startedState` property |\n\n## Comment API\nThe comment API for publishing a comment as an author, or getting comment updates. `Comment`, `Vote` and `CommentEdit` inherit `Publication` class and all have a similar API. A `Comment` updates itselfs on update events after `Comment.update()` is called if `Comment.cid` exists.\n\n### `comment.publish()`\n\n\u003e Publish the comment to the pubsub. You must then wait for the `'challenge'` event and answer with a `ChallengeAnswer`.\n\n#### Example\n\n```js\nconst comment = await pkc.createComment(commentObject)\ncomment.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  comment.publishChallengeAnswers(challengeAnswers)\n})\ncomment.on('challengeverification', console.log)\nawait comment.publish()\n```\n\n### `comment.publishChallengeAnswers(challengeAnswers)`\n\n\u003e Publish your answers to the challenges e.g. the captcha answers.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| challengeAnswers | `string[]` | The challenge answers |\n\n#### Example\n\n```js\nconst comment = await pkc.createComment(commentObject)\ncomment.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  comment.publishChallengeAnswers(challengeAnswers)\n})\ncomment.on('challengeverification', console.log)\nawait comment.publish()\n```\n\n### `comment.update()`\n\n\u003e Start polling the network for comment updates (replies, upvotes, edits, etc), update itself and emit the update event. Only usable if comment.cid or exists.\n\n#### Example\n\n```js\nconst commentCid = 'Qm...'\nconst comment = await pkc.getComment({cid: commentCid})\ncomment.on('update', (updatedCommentInstance) =\u003e {\n  console.log(updatedCommentInstance)\n\n  // if you want to stop polling for new updates after only the first one\n  comment.stop()\n})\ncomment.update()\n\n// if you already fetched the comment and only want the updates\nconst commentDataFetchedEarlier = {content, author, cid, ...comment}\nconst comment = await pkc.createComment(commentDataFetchedEarlier)\ncomment.on('update', () =\u003e {\n  console.log('the comment instance updated itself:', comment)\n})\ncomment.update()\n```\n\n### `comment.stop()`\n\n\u003e Stop polling the network for new comment updates started by comment.update().\n\n## Comment Events\nThe comment events.\n\n### `update`\n\n\u003e The comment has been updated, which means vote counts and replies may have changed. To start polling the network for updates, call `Comment.update()`. If the previous `CommentUpdate` is the same, do not emit `update`.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `Comment` | The updated `Comment`, i.e. itself, `this` |\n\nObject is of the form:\n\n```js\n{ // ...TODO }\n```\n\n#### Example\n\n```js\nconst comment = await pkc.getComment({cid: commentCid})\ncomment.on('update', (updatedComment) =\u003e {\n  console.log(updatedComment)\n})\ncomment.update()\n\n// stop looking for updates after 10 minutes\nsetTimeout(() =\u003e comment.stop(), 60000)\n```\n\n### `challenge`\n\n\u003e After publishing a comment, the community owner will send back a `challenge`, eg. a captcha that the user must complete.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `ChallengeMessage` | The challenge the user must complete |\n| `Comment` | The `Comment` instance, i.e. `this` |\n\nObject is of the form:\n\n```js\n{ // ...TODO }\n```\n\n#### Example\n\n```js\nconst comment = await pkc.createComment(commentObject)\ncomment.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  comment.publishChallengeAnswers(challengeAnswers)\n})\ncomment.on('challengeverification', console.log)\nawait comment.publish()\n```\n\n### `challengeverification`\n\n\u003e After publishing a challenge answer, the community owner will send back a `challengeverification` to let the network know if the challenge was completed successfully.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `ChallengeVerificationMessage` | The challenge verification result |\n| `Comment` or `undefined` | The `Comment` instance if the publication is a comment and the verification contains comment data, otherwise `undefined` |\n\nObject is of the form:\n\n```js\n{ // ...TODO }\n```\n\n#### Example\n\n```js\nconst comment = await pkc.createComment(commentObject)\ncomment.on('challenge', async (challengeMessage) =\u003e {\n  const challengeAnswers = await askUserForChallengeAnswers(challengeMessage.challenges)\n  comment.publishChallengeAnswers(challengeAnswers)\n})\ncomment.on('challengeverification', (challengeVerification) =\u003e console.log('published post cid is', challengeVerification?.publication?.cid))\nawait comment.publish()\n```\n\n### `statechange`\n\n\u003e `Comment.state` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `'stopped' \\| 'updating' \\| 'publishing'` | The `Comment.state` property |\n\n### `updatingstatechange`\n\n\u003e `Comment.updatingState` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `'stopped' \\| 'resolving-author-address' \\| 'fetching-ipfs' \\| 'fetching-update-ipns' \\| 'fetching-update-ipfs' \\| 'failed' \\| 'succeeded'` | The `Comment.updatingState` property |\n\n### `publishingstatechange`\n\n\u003e `Comment.publishingState` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `'stopped' \\| 'resolving-community-address' \\| 'fetching-community-ipns' \\| 'fetching-community-ipfs' \\| 'publishing-challenge-request' \\| 'waiting-challenge' \\| 'waiting-challenge-answers' \\| 'publishing-challenge-answer' \\| 'waiting-challenge-verification' \\| 'failed' \\| 'succeeded'` | The `Comment.publishingState` property |\n\n## Pages API\nThe pages API for scrolling pages of a community or replies to a post/comment. `Community.posts` and `Comment.replies` are `Pages` instances. `Community.posts.pages.hot` is a `Page` instance.\n\n### `pages.getPage({cid})`\n\n\u003e Get a `Page` instance using an IPFS CID from `Pages.pageCids[sortType]`, e.g. `Community.posts.pageCids.hot` or `Comment.replies.pageCids.best`.\n\n#### Parameters\n\n| Name | Type | Description |\n| ---- | ---- | ----------- |\n| cid | `string` | The IPFS CID of the page |\n\n#### Returns\n\n| Type | Description |\n| -------- | -------- |\n| `Promise\u003cPage\u003e` | A `Page` instance |\n\n#### Example\n\n```js\n// get sorted posts in a community\nconst community = await pkc.getCommunity({address: communityAddress})\nconst pageSortedByTopYear = await community.posts.getPage({cid: community.posts.pageCids.topYear})\nconst postsSortedByTopYear = pageSortedByTopYear.comments\nconsole.log(postsSortedByTopYear)\n\n// get sorted replies to a post or comment\nconst post = await pkc.getComment({cid: commentCid})\npost.on('update', async updatedPost =\u003e {\n  let replies\n  // try to get sorted replies by sort type 'new'\n  // sorted replies pages are not always available, for example if the post only has a few replies\n  if (updatedPost.replies?.pageCids?.new) {\n    const repliesPageSortedByNew = await updatedPost.replies.getPage({cid: updatedPost.replies.pageCids.new})\n    replies = repliesPageSortedByNew.comments\n  }\n  else {\n    // the 'best' sort type is always preloaded by default on replies and can be used as fallback\n    // on community.posts only 'hot' is preloaded by default\n    replies = updatedPost.replies.pages.best.comments\n  }\n  console.log(replies)\n})\n```\n\n## Client Events\nThe client events.\n\n### `statechange`\n\n\u003e `Client.state` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `'stopped' \\| 'resolving-author-address' \\| 'fetching-ipfs' \\| 'fetching-update-ipns' \\| 'fetching-update-ipfs' \\| 'resolving-community-address' \\| 'fetching-community-ipns' \\| 'fetching-community-ipfs' \\| 'subscribing-pubsub' \\| 'publishing-challenge-request' \\| 'waiting-challenge' \\| 'waiting-challenge-answers' \\| 'publishing-challenge-answer' \\| 'waiting-challenge-verification' \\| 'connecting' \\| 'connected'` | The `Client.state` property |\n\n#### Example\n\n```js\nconst onStateChange = (state) =\u003e console.log('client state changed:', state)\nfor (const clientUrl in clients?.ipfsGateways) {\n  comment.clients?.ipfsGateways?.[clientUrl].on('statechange', onStateChange)\n}\nfor (const clientUrl in clients?.ipfsClients) {\n  comment.clients?.ipfsClients?.[clientUrl].on('statechange', onStateChange)\n}\nfor (const clientUrl in clients?.pubsubClients) {\n  comment.clients?.pubsubClients?.[clientUrl].on('statechange', onStateChange)\n}\nfor (const chainTicker in clients?.chainProviders) {\n  for (const clientUrl in clients?.chainProviders?.[chainTicker]) {\n    comment.clients?.chainProviders?.[chainTicker]?.[clientUrl].on('statechange', onStateChange)\n  }\n}\n```\n\n### `settingschange`\n\n\u003e `Client.settings` property changed.\n\n#### Emits\n\n| Type | Description |\n| -------- | -------- |\n| `PKCRpcSettings` | The `Client.settings` property |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpkcprotocol%2Fpkc-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpkcprotocol%2Fpkc-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpkcprotocol%2Fpkc-js/lists"}