├── .github
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── ci.yml
│ └── deploy.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── DEVELOPING.md
├── README.md
├── contracts
├── package.json
├── src
│ └── common.ts
└── tsconfig.json
├── docs
├── .vitepress
│ ├── components
│ │ ├── BlogRedirect.vue
│ │ ├── CodeGroupOpener.vue
│ │ ├── EmailForm.vue
│ │ ├── FeatureCard.vue
│ │ ├── FeatureRow.vue
│ │ ├── FeatureTags.vue
│ │ ├── HeroAction.vue
│ │ ├── HeroImage.vue
│ │ ├── HeroRow.vue
│ │ ├── HomepageBanner.vue
│ │ ├── HomepageFooter.vue
│ │ ├── Iframe.vue
│ │ ├── TextItem.vue
│ │ ├── TextRow.vue
│ │ ├── Version.vue
│ │ ├── icon_logo.png
│ │ └── icon_logo_dark.png
│ ├── config.ts
│ ├── getSidebar.ts
│ └── theme
│ │ ├── index.ts
│ │ └── style.css
├── 1-introduction.md
├── 2-applications.md
├── 3-querying.md
├── 4-identities-auth.md
├── 5-managing-sessions.md
├── 6-deploying.md
├── 7-upgrading.md
├── 8-extra.md
├── api
│ ├── cli.md
│ ├── core.md
│ ├── gossiplog.md
│ ├── hooks.md
│ ├── interfaces.md
│ ├── modeldb.md
│ ├── signatures.md
│ ├── signer-atp.md
│ ├── signer-cosmos.md
│ ├── signer-ethereum-viem.md
│ ├── signer-ethereum.md
│ ├── signer-solana.md
│ └── signer-substrate.md
├── blog.md
├── blog
│ ├── 2023-05-04-merklizing-the-key-value-store.md
│ └── 2024-05-13-metropolis.md
├── examples-chat-next.md
├── examples-chat.md
├── examples-encrypted-chat.md
├── examples-forum.md
├── examples-snake.md
├── index.md
├── public
│ ├── contract_code.png
│ ├── fonts
│ │ ├── Geist-Black.woff
│ │ ├── Geist-Black.woff2
│ │ ├── Geist-Bold.woff
│ │ ├── Geist-Bold.woff2
│ │ ├── Geist-Light.woff
│ │ ├── Geist-Light.woff2
│ │ ├── Geist-Medium.woff
│ │ ├── Geist-Medium.woff2
│ │ ├── Geist-Regular.woff
│ │ ├── Geist-Regular.woff2
│ │ ├── Geist-SemiBold.woff
│ │ ├── Geist-SemiBold.woff2
│ │ ├── Geist-Thin.woff
│ │ ├── Geist-Thin.woff2
│ │ ├── Geist-UltraBlack.woff
│ │ ├── Geist-UltraBlack.woff2
│ │ ├── Geist-UltraLight.woff
│ │ ├── Geist-UltraLight.woff2
│ │ ├── GeistMono-Black.woff
│ │ ├── GeistMono-Black.woff2
│ │ ├── GeistMono-Bold.woff
│ │ ├── GeistMono-Bold.woff2
│ │ ├── GeistMono-Light.woff
│ │ ├── GeistMono-Light.woff2
│ │ ├── GeistMono-Medium.woff
│ │ ├── GeistMono-Medium.woff2
│ │ ├── GeistMono-Regular.woff
│ │ ├── GeistMono-Regular.woff2
│ │ ├── GeistMono-SemiBold.woff
│ │ ├── GeistMono-SemiBold.woff2
│ │ ├── GeistMono-Thin.woff
│ │ ├── GeistMono-Thin.woff2
│ │ ├── GeistMono-UltraBlack.woff
│ │ ├── GeistMono-UltraBlack.woff2
│ │ ├── GeistMono-UltraLight.woff
│ │ └── GeistMono-UltraLight.woff2
│ ├── gossiplog.png
│ ├── graphic_jellyfish.png
│ ├── graphic_jellyfish_dark.png
│ ├── graphic_mainframe_1.png
│ ├── graphic_mainframe_2.png
│ ├── graphic_mainframe_3.png
│ ├── graphic_mainframe_4.png
│ ├── icon_computer.png
│ ├── icon_computer_2.png
│ ├── icon_jellyfish.png
│ ├── icon_logo.png
│ ├── icon_logo_dark.png
│ ├── icon_mainframe.png
│ ├── logo.png
│ └── run_migrations.png
└── ref
│ ├── consistency.md
│ ├── contract.md
│ ├── faq.md
│ ├── model.md
│ └── roadmap.md
├── eslint.config.mjs
├── examples
├── chat-next
│ ├── README.md
│ ├── app
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── next-env.d.ts
│ ├── next.config.mjs
│ ├── package.json
│ ├── postcss.config.cjs
│ ├── railway.json
│ ├── server.ts
│ ├── tsconfig.json
│ └── window.d.ts
├── chat
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ ├── .well-known
│ │ │ └── farcaster.json
│ │ ├── icon.png
│ │ ├── image.png
│ │ └── splash.png
│ ├── railway.json
│ ├── src
│ │ ├── App.tsx
│ │ ├── AppContext.ts
│ │ ├── Chat.tsx
│ │ ├── ConnectionStatus.tsx
│ │ ├── ControlPanel.tsx
│ │ ├── LogStatus.tsx
│ │ ├── MessageComposer.tsx
│ │ ├── SessionStatus.tsx
│ │ ├── components
│ │ │ ├── AddressView.tsx
│ │ │ ├── MultiaddrView.tsx
│ │ │ └── PeerIdView.tsx
│ │ ├── connect
│ │ │ ├── ConnectATP.tsx
│ │ │ ├── ConnectCosmosEvmMetamask.tsx
│ │ │ ├── ConnectCosmosKeplr.tsx
│ │ │ ├── ConnectEIP712.tsx
│ │ │ ├── ConnectEIP712Burner.tsx
│ │ │ ├── ConnectEthereumKeplr.tsx
│ │ │ ├── ConnectLeap.tsx
│ │ │ ├── ConnectMagic.tsx
│ │ │ ├── ConnectPolkadot.tsx
│ │ │ ├── ConnectSIWE.tsx
│ │ │ ├── ConnectSIWEBurner.tsx
│ │ │ ├── ConnectSIWEViem.tsx
│ │ │ ├── ConnectSIWF.tsx
│ │ │ ├── ConnectSolana.tsx
│ │ │ ├── ConnectTerra.tsx
│ │ │ └── index.tsx
│ │ ├── contract.ts
│ │ └── index.tsx
│ ├── styles.css
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── vite.config.js
├── encrypted-chat
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── railway.json
│ ├── src
│ │ ├── App.tsx
│ │ ├── components
│ │ │ └── privkeys.ts
│ │ ├── contract.ts
│ │ ├── index.css
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.js
├── forum
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ ├── .well-known
│ │ │ └── farcaster.json
│ │ ├── icon.png
│ │ ├── image.png
│ │ └── splash.png
│ ├── railway.json
│ ├── src
│ │ ├── App.tsx
│ │ ├── AppContext.ts
│ │ ├── Layout.tsx
│ │ ├── MessageComposer.tsx
│ │ ├── contract.ts
│ │ └── index.tsx
│ ├── styles.css
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── vite.config.js
├── refs
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ ├── icon.png
│ │ ├── image.png
│ │ └── splash.png
│ ├── railway.json
│ ├── src
│ │ ├── AppContext.ts
│ │ ├── MyProfile.tsx
│ │ ├── index.tsx
│ │ └── refs.ts
│ ├── styles.css
│ ├── tailwind.config.js
│ ├── test
│ │ ├── refs.test.ts
│ │ └── tsconfig.json
│ ├── tsconfig.json
│ └── vite.config.js
└── starter
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ ├── icon.png
│ ├── image.png
│ └── splash.png
│ ├── railway.json
│ ├── src
│ ├── App.tsx
│ ├── AppContext.ts
│ ├── Layout.tsx
│ ├── contract.ts
│ └── index.tsx
│ ├── styles.css
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── vite.config.js
├── install-prod.sh
├── install.sh
├── package-lock.json
├── package.json
├── packages
├── bootstrap-peer
│ ├── Dockerfile
│ ├── README.md
│ ├── create-peer-id.js
│ ├── deploy-mainnet.sh
│ ├── deploy-testnet.sh
│ ├── mainnet
│ │ ├── p0.fly.toml
│ │ ├── p1.fly.toml
│ │ └── p2.fly.toml
│ ├── monitor.sh
│ ├── package.json
│ ├── src
│ │ ├── api.ts
│ │ ├── config.ts
│ │ ├── index.ts
│ │ └── libp2p.ts
│ ├── testnet
│ │ ├── p0.fly.toml
│ │ ├── p1.fly.toml
│ │ └── p2.fly.toml
│ └── tsconfig.json
├── cli
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── AppInstance.ts
│ │ ├── commands
│ │ │ ├── export.ts
│ │ │ ├── import.ts
│ │ │ ├── index.ts
│ │ │ ├── info.ts
│ │ │ └── run.ts
│ │ ├── index.ts
│ │ ├── prompt.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── client
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── Client.ts
│ │ ├── index.ts
│ │ └── useLiveQuery.ts
│ └── tsconfig.json
├── core
│ ├── README.md
│ ├── ava.config.js
│ ├── internal_docs
│ │ ├── branches.md
│ │ └── images
│ │ │ ├── step_1.svg
│ │ │ ├── step_2.svg
│ │ │ ├── step_3.svg
│ │ │ ├── step_4.svg
│ │ │ ├── step_5.svg
│ │ │ ├── step_6.svg
│ │ │ └── step_7.svg
│ ├── package.json
│ ├── src
│ │ ├── Canvas.ts
│ │ ├── ExecutionContext.ts
│ │ ├── View.ts
│ │ ├── api.ts
│ │ ├── compatibility.ts
│ │ ├── constants.ts
│ │ ├── contract.ts
│ │ ├── index.ts
│ │ ├── migrations.ts
│ │ ├── random.ts
│ │ ├── runtime
│ │ │ ├── AbstractRuntime.ts
│ │ │ ├── ClassContractRuntime.ts
│ │ │ ├── ClassFunctionRuntime.ts
│ │ │ ├── index.ts
│ │ │ └── rules.ts
│ │ ├── schema.ts
│ │ ├── snapshot.ts
│ │ ├── synchronous.ts
│ │ ├── targets
│ │ │ ├── browser
│ │ │ │ └── index.ts
│ │ │ ├── default
│ │ │ │ └── index.ts
│ │ │ ├── interface.ts
│ │ │ ├── node
│ │ │ │ └── index.ts
│ │ │ ├── react-native
│ │ │ │ └── index.ts
│ │ │ └── worker
│ │ │ │ └── index.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── test
│ │ ├── build.test.ts
│ │ ├── contract.basic.test.ts
│ │ ├── contract.compat.test.ts
│ │ ├── contract.instance.test.ts
│ │ ├── contract.rules.test.ts
│ │ ├── contract.topic.test.ts
│ │ ├── effects.test.ts
│ │ ├── modelApi.basic.test.ts
│ │ ├── modelApi.id.test.ts
│ │ ├── modelApi.link.test.ts
│ │ ├── modelApi.merge.test.ts
│ │ ├── modelApi.random.test.ts
│ │ ├── modelApi.transaction.test.ts
│ │ ├── signers.test.ts
│ │ ├── snapshot.test.ts
│ │ ├── synchronous.test.ts
│ │ ├── tsconfig.json
│ │ └── utils.ts
│ ├── tsconfig.json
│ └── types
│ │ └── prng-xoshiro.d.ts
├── ethereum-contracts
│ ├── .gitignore
│ ├── README.md
│ ├── contracts
│ │ ├── Test.sol
│ │ └── libraries
│ │ │ ├── EIP712Signer.sol
│ │ │ ├── Hashers.sol
│ │ │ └── Verifiers.sol
│ ├── hardhat.config.ts
│ ├── package.json
│ ├── test
│ │ └── Test.ts
│ └── tsconfig.json
├── gossiplog
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── AbstractGossipLog.ts
│ │ ├── AncestorIndex.ts
│ │ ├── MerkleIndex.ts
│ │ ├── MessageId.ts
│ │ ├── MessageSet.ts
│ │ ├── SignedMessage.ts
│ │ ├── api.ts
│ │ ├── bootstrap.ts
│ │ ├── client
│ │ │ └── index.ts
│ │ ├── clock.ts
│ │ ├── constants.ts
│ │ ├── do
│ │ │ └── index.ts
│ │ ├── errors.ts
│ │ ├── idb
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── interface.ts
│ │ ├── libp2p
│ │ │ ├── index.ts
│ │ │ ├── interface.ts
│ │ │ └── service.ts
│ │ ├── migrations.ts
│ │ ├── pg
│ │ │ └── index.ts
│ │ ├── protocols
│ │ │ ├── events.proto
│ │ │ ├── events.ts
│ │ │ ├── sync.proto
│ │ │ └── sync.ts
│ │ ├── server
│ │ │ └── index.ts
│ │ ├── sqlite-expo
│ │ │ └── index.ts
│ │ ├── sqlite
│ │ │ └── index.ts
│ │ ├── sync
│ │ │ ├── client.ts
│ │ │ ├── driver.ts
│ │ │ ├── index.ts
│ │ │ ├── server.ts
│ │ │ └── utils.ts
│ │ ├── targets
│ │ │ ├── browser
│ │ │ │ ├── index.ts
│ │ │ │ ├── libp2p.ts
│ │ │ │ └── privateKey.ts
│ │ │ ├── default
│ │ │ │ ├── index.ts
│ │ │ │ └── libp2p.ts
│ │ │ ├── interface.ts
│ │ │ ├── node
│ │ │ │ ├── index.ts
│ │ │ │ ├── libp2p.ts
│ │ │ │ └── privateKey.ts
│ │ │ └── react-native
│ │ │ │ ├── index.ts
│ │ │ │ └── libp2p.ts
│ │ └── utils.ts
│ ├── test
│ │ ├── ancestors.test.ts
│ │ ├── benchmarks.test.ts
│ │ ├── clock.test.ts
│ │ ├── connect.test.ts
│ │ ├── gossip.test.ts
│ │ ├── iterate.test.ts
│ │ ├── libp2p.ts
│ │ ├── messages.test.ts
│ │ ├── result.test.ts
│ │ ├── sort.test.ts
│ │ ├── sync.test.ts
│ │ ├── tsconfig.json
│ │ ├── utils.ts
│ │ ├── web-locks.d.ts
│ │ └── worker.ts
│ ├── tsconfig.json
│ └── wrangler.toml
├── hooks
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── AppInfo.tsx
│ │ ├── auth
│ │ │ ├── AuthContext.tsx
│ │ │ ├── index.ts
│ │ │ ├── styles.ts
│ │ │ ├── useLogout.tsx
│ │ │ ├── useSIWE.tsx
│ │ │ └── useSIWF.tsx
│ │ ├── components.ts
│ │ ├── index.ts
│ │ ├── provider
│ │ │ ├── hooks.tsx
│ │ │ ├── index.ts
│ │ │ └── provider.tsx
│ │ ├── useCanvas.ts
│ │ ├── useClock.ts
│ │ ├── useLiveQuery.ts
│ │ ├── useSyncStatus.ts
│ │ └── useTick.ts
│ └── tsconfig.json
├── interfaces
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── Action.ts
│ │ ├── Awaitable.ts
│ │ ├── Message.ts
│ │ ├── MessageType.ts
│ │ ├── Session.ts
│ │ ├── SessionSigner.ts
│ │ ├── Signature.ts
│ │ ├── Signer.ts
│ │ ├── SignerCache.ts
│ │ ├── Snapshot.ts
│ │ └── index.ts
│ ├── test
│ │ ├── signers.test.ts
│ │ └── tsconfig.json
│ └── tsconfig.json
├── modeldb-durable-objects
│ ├── package.json
│ ├── src
│ │ ├── ModelAPI.ts
│ │ ├── ModelDB.ts
│ │ ├── ModelDBProxy.ts
│ │ ├── ModelDBProxyObject.ts
│ │ ├── ModelDBProxyWorker.ts
│ │ ├── RelationAPI.ts
│ │ ├── encoding.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ ├── tsconfig.json
│ └── wrangler.toml
├── modeldb-idb
│ ├── package.json
│ ├── src
│ │ ├── ModelAPI.ts
│ │ ├── ModelDB.ts
│ │ ├── encoding.ts
│ │ ├── index.ts
│ │ ├── useLiveQuery.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── modeldb-pg
│ ├── package.json
│ ├── src
│ │ ├── ModelAPI.ts
│ │ ├── ModelDB.ts
│ │ ├── RelationAPI.ts
│ │ ├── encoding.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── modeldb-sqlite-expo
│ ├── package.json
│ ├── src
│ │ ├── ModelAPI.ts
│ │ ├── ModelDB.ts
│ │ ├── RelationAPI.ts
│ │ ├── encoding.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── modeldb-sqlite-wasm
│ ├── package.json
│ ├── src
│ │ ├── ModelAPI.ts
│ │ ├── ModelDB.ts
│ │ ├── RelationAPI.ts
│ │ ├── encoding.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── modeldb-sqlite
│ ├── package.json
│ ├── src
│ │ ├── ModelAPI.ts
│ │ ├── ModelDB.ts
│ │ ├── RelationAPI.ts
│ │ ├── encoding.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── modeldb
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── AbstractModelDB.ts
│ │ ├── config.ts
│ │ ├── index.ts
│ │ ├── query.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── test
│ │ ├── config.test.ts
│ │ ├── count.test.ts
│ │ ├── create.test.ts
│ │ ├── example.test.ts
│ │ ├── getmany.test.ts
│ │ ├── indexes.test.ts
│ │ ├── iterate.test.ts
│ │ ├── merge.test.ts
│ │ ├── migrations.test.ts
│ │ ├── operations.test.ts
│ │ ├── query.test.ts
│ │ ├── relations.test.ts
│ │ ├── subscriptions.test.ts
│ │ ├── transactions.test.ts
│ │ ├── tsconfig.json
│ │ ├── utils.ts
│ │ └── worker-durable-objects.ts
│ ├── tsconfig.json
│ └── wrangler.toml
├── network-explorer
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── src
│ │ ├── ActionsTable.tsx
│ │ ├── AdminView.tsx
│ │ ├── App.tsx
│ │ ├── ContractView.tsx
│ │ ├── LandingPage.tsx
│ │ ├── ModelTable.tsx
│ │ ├── columndefMeta.d.ts
│ │ ├── components
│ │ │ ├── ApplicationData.tsx
│ │ │ ├── BinaryCellData.tsx
│ │ │ ├── ClickableChecklistItem.tsx
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── DidPopover.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Navbar.tsx
│ │ │ ├── PaginationButton.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ ├── StagedMigrationsBottomSheet.tsx
│ │ │ ├── TableSelector.tsx
│ │ │ ├── TextFilterMenu.tsx
│ │ │ ├── ThemeToggle.tsx
│ │ │ └── table
│ │ │ │ ├── ColumnsDropdown.tsx
│ │ │ │ ├── EditableCell.tsx
│ │ │ │ ├── EditableRow.tsx
│ │ │ │ ├── FiltersDropdown.tsx
│ │ │ │ ├── PaginationControl.tsx
│ │ │ │ ├── SortSelector.tsx
│ │ │ │ ├── Table.tsx
│ │ │ │ └── elements.tsx
│ │ ├── hooks
│ │ │ ├── useApplicationData.tsx
│ │ │ ├── useChangedRows.tsx
│ │ │ ├── useCodeMirror.ts
│ │ │ ├── useContractData.tsx
│ │ │ ├── useCursorStack.ts
│ │ │ ├── useKeyboardNavigation.ts
│ │ │ ├── usePageTitle.tsx
│ │ │ ├── useSearchFilters.ts
│ │ │ ├── useStagedMigrations.tsx
│ │ │ └── useTheme.tsx
│ │ ├── index.css
│ │ ├── main.tsx
│ │ ├── tables.ts
│ │ ├── utils.ts
│ │ └── vite-env.d.ts
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ ├── tsconfig.vercel.json
│ └── vite.config.ts
├── relay-server
│ ├── Dockerfile
│ ├── README.md
│ ├── create-peer-id.js
│ ├── deploy-mainnet.sh
│ ├── deploy-testnet.sh
│ ├── mainnet
│ │ └── fly.toml
│ ├── package.json
│ ├── src
│ │ ├── api.ts
│ │ ├── config.ts
│ │ ├── index.ts
│ │ └── libp2p.ts
│ ├── testnet
│ │ └── fly.toml
│ └── tsconfig.json
├── signatures
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── AbstractSessionSigner.ts
│ │ ├── ed25519.ts
│ │ ├── index.ts
│ │ ├── snapshot.ts
│ │ ├── targets
│ │ │ ├── browser
│ │ │ │ └── index.ts
│ │ │ ├── default
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── node
│ │ │ │ └── index.ts
│ │ │ └── react-native
│ │ │ │ └── index.ts
│ │ └── utils.ts
│ ├── test
│ │ ├── tsconfig.json
│ │ └── utils.test.ts
│ └── tsconfig.json
├── signer-atp
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── ATPSigner.ts
│ │ ├── commit.ts
│ │ ├── index.ts
│ │ ├── mst.ts
│ │ └── operation.ts
│ ├── test
│ │ ├── README.md
│ │ ├── archives
│ │ │ └── 3kgvb53cuh22l.car
│ │ ├── fixture.json
│ │ ├── generateTestData.ts
│ │ ├── plcOperationLog.json
│ │ ├── signer.test.ts
│ │ └── tsconfig.json
│ └── tsconfig.json
├── signer-cosmos
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── CosmosSigner.ts
│ │ ├── external_signers
│ │ │ ├── amino.ts
│ │ │ ├── arbitrary.ts
│ │ │ ├── bytes.ts
│ │ │ ├── default.ts
│ │ │ └── ethereum.ts
│ │ ├── index.ts
│ │ ├── signatureData.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── test
│ │ ├── CosmosSigner.test.ts
│ │ └── tsconfig.json
│ └── tsconfig.json
├── signer-ethereum-viem
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── SIWESignerViem.ts
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── test
│ │ ├── signer.test.ts
│ │ └── tsconfig.json
│ └── tsconfig.json
├── signer-ethereum
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── eip712
│ │ │ ├── Eip712Signer.ts
│ │ │ ├── Secp256k1DelegateSigner.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── index.ts
│ │ ├── siwe
│ │ │ ├── SIWESigner.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── siwf
│ │ │ ├── SIWFSigner.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ ├── test
│ │ ├── EIP712Signer.test.ts
│ │ ├── SIWESigner.test.ts
│ │ ├── SIWFSigner.test.ts
│ │ └── tsconfig.json
│ └── tsconfig.json
├── signer-solana
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── SolanaSigner.ts
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── test
│ │ ├── signer.test.ts
│ │ ├── tsconfig.json
│ │ └── utils.ts
│ └── tsconfig.json
├── signer-substrate
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ │ ├── SubstrateSigner.ts
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── test
│ │ ├── SubstrateSigner.test.ts
│ │ └── tsconfig.json
│ └── tsconfig.json
├── test-network
│ ├── .dockerignore
│ ├── .gitignore
│ ├── README.md
│ ├── client-libp2p
│ │ ├── Dockerfile
│ │ ├── client
│ │ │ ├── index.html
│ │ │ ├── src
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ ├── fly.toml
│ │ └── worker
│ │ │ ├── src
│ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ ├── client-ws
│ │ ├── client
│ │ │ ├── index.html
│ │ │ ├── src
│ │ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ │ └── worker
│ │ │ ├── src
│ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ ├── create-peer-id.js
│ ├── dashboard
│ │ ├── Dockerfile
│ │ ├── client
│ │ │ ├── index.html
│ │ │ ├── src
│ │ │ │ ├── App.tsx
│ │ │ │ ├── EventLog.tsx
│ │ │ │ ├── Graph.tsx
│ │ │ │ ├── WorkerList.tsx
│ │ │ │ └── index.tsx
│ │ │ └── tsconfig.json
│ │ ├── fly.toml
│ │ └── server
│ │ │ ├── src
│ │ │ └── index.ts
│ │ │ └── tsconfig.json
│ ├── docker-compose.base.yml
│ ├── docker-compose.sh
│ ├── package.json
│ ├── relay
│ │ ├── Dockerfile
│ │ ├── fly.toml
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── rendezvous
│ │ ├── Dockerfile
│ │ ├── fly.toml
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── scripts
│ │ ├── spawn.sh
│ │ ├── start.sh
│ │ └── stop.sh
│ ├── server-libp2p
│ │ ├── Dockerfile
│ │ ├── fly.toml
│ │ ├── src
│ │ │ ├── config.ts
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── server-ws
│ │ ├── Dockerfile
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── src
│ │ ├── constants.ts
│ │ ├── events.ts
│ │ ├── socket-peer.ts
│ │ └── socket-worker.ts
│ └── tsconfig.json
└── vm
│ ├── README.md
│ ├── ava.config.js
│ ├── package.json
│ ├── src
│ ├── error.ts
│ ├── index.ts
│ └── vm.ts
│ ├── test
│ ├── contracts.test.ts
│ ├── errors.test.ts
│ ├── functions.test.ts
│ ├── tsconfig.json
│ └── values.test.ts
│ └── tsconfig.json
├── pnpm-workspace.yaml
├── scripts
├── decodeMessageId.js
├── generateLibp2pPrivkey.js
└── setupPostgresTestEnv.cjs
├── tsconfig.base.json
├── tsconfig.json
├── uninstall.sh
├── upgrade.sh
└── version.sh
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## How has this been tested?
4 |
5 | - [ ] CI tests pass
6 | - [ ] Tested with example-chat (including login, all signers, and exchanging messages)
7 | - [ ] Tested with `@canvas-js/test-network`: (optional)
8 |
9 | ## Does this contain any breaking changes to external interfaces?
10 |
11 | - [ ] Contract interfaces
12 | - [ ] Core interface
13 | - [ ] CLI
14 | - [ ] Data storage formats, including IndexedDB, SQLite, or filesystem storage (will this break existing apps?)
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | .env.*
3 | .DS_Store
4 | .next/
5 | .vscode/
6 | pnpm-lock.yaml
7 |
8 | node_modules/
9 |
10 | tsconfig.tsbuildinfo
11 | tsconfig.node.tsbuildinfo
12 |
13 | contracts/lib/
14 | contracts/dist/
15 | contracts/.cache/
16 |
17 | packages/**/lib/
18 | packages/**/dist/
19 | packages/**/.cache/
20 |
21 | examples/**/lib/
22 | examples/**/dist/
23 | examples/**/.cache/
24 |
25 | docs/.vitepress/dist
26 | docs/.vitepress/cache
27 | docs/.vitepress/.temp
28 |
29 | .vercel
30 | .wrangler
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | link-workspace-packages=true
2 | prefer-workspace-packages=true
3 | link-workspace-packages=deep
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v22
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Config files
2 | .env
3 | .next/
4 | node_modules/
5 | package-lock.json
6 | docs/
7 | packages/gossiplog/src/protocols/
8 | examples/*/vite.config.d.ts
9 | examples/*/vite.config.js
10 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "semi": false,
4 | "printWidth": 120,
5 | "trailingComma": "all",
6 | "overrides": [
7 | {
8 | "files": "*.md",
9 | "options": {
10 | "useTabs": false,
11 | "tabWidth": 2
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/contracts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/contracts",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "scripts": {
16 | "test": "ava --serial"
17 | },
18 | "dependencies": {
19 | "@canvas-js/core": "0.16.1",
20 | "@canvas-js/modeldb": "0.16.1",
21 | "@canvas-js/signer-ethereum": "0.16.1",
22 | "@canvas-js/utils": "1.0.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/contracts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "lib"
6 | },
7 | "references": [{ "path": "../packages/core" }, { "path": "../packages/modeldb" }, { "path": "../packages/signer-ethereum" }]
8 | }
9 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/BlogRedirect.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/CodeGroupOpener.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/HomepageBanner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Pre-Release Documentation
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/HomepageFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Iframe.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/TextItem.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | {{ prefix }}:
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/TextRow.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
{{ title }}
11 |
{{ details.replace(/\\n/g, '\n') }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
50 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/Version.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | {{ version }}
7 |
8 |
--------------------------------------------------------------------------------
/docs/.vitepress/components/icon_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/.vitepress/components/icon_logo.png
--------------------------------------------------------------------------------
/docs/.vitepress/components/icon_logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/.vitepress/components/icon_logo_dark.png
--------------------------------------------------------------------------------
/docs/1-introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Canvas applications are built on a programmable multi-writer relational database. They're easy to configure and automatically have several useful properties:
4 |
5 | - **Real-time**. Nodes connect and sync directly with each other over WebSockets, and optionally can use GossipSub to broadcast actions over an open mesh of servers.
6 | - **Convergent**. Actions can freely read and write to a relational database with [strong eventual consistency](https://en.wikipedia.org/wiki/Eventual_consistency). Every peer's database state will converge to the same state, regardless of the order in which the actions are received.
7 | - **Self-authenticating**. Every action is signed with a cryptographically verifiable user identity, and attributable to a user's DID. The entire action history of an application can be replayed and verified.
8 | - **Lock-free**. Any number of peers can concurrently execute new actions, without waiting for consensus.
9 | - **Cross-platform**. Canvas apps run in the browser, desktop, or React Native, persisting data with IndexedDB, SQLite in WASM, native SQLite, or Postgres.
--------------------------------------------------------------------------------
/docs/api/cli.md:
--------------------------------------------------------------------------------
1 | ../../packages/cli/README.md
--------------------------------------------------------------------------------
/docs/api/core.md:
--------------------------------------------------------------------------------
1 | ../../packages/core/README.md
--------------------------------------------------------------------------------
/docs/api/gossiplog.md:
--------------------------------------------------------------------------------
1 | ../../packages/gossiplog/README.md
--------------------------------------------------------------------------------
/docs/api/hooks.md:
--------------------------------------------------------------------------------
1 | ../../packages/hooks/README.md
--------------------------------------------------------------------------------
/docs/api/interfaces.md:
--------------------------------------------------------------------------------
1 | ../../packages/interfaces/README.md
--------------------------------------------------------------------------------
/docs/api/modeldb.md:
--------------------------------------------------------------------------------
1 | ../../packages/modeldb/README.md
--------------------------------------------------------------------------------
/docs/api/signatures.md:
--------------------------------------------------------------------------------
1 | ../../packages/signatures/README.md
--------------------------------------------------------------------------------
/docs/api/signer-atp.md:
--------------------------------------------------------------------------------
1 | ../../packages/signer-atp/README.md
--------------------------------------------------------------------------------
/docs/api/signer-cosmos.md:
--------------------------------------------------------------------------------
1 | ../../packages/signer-cosmos/README.md
--------------------------------------------------------------------------------
/docs/api/signer-ethereum-viem.md:
--------------------------------------------------------------------------------
1 | ../../packages/signer-ethereum-viem/README.md
--------------------------------------------------------------------------------
/docs/api/signer-ethereum.md:
--------------------------------------------------------------------------------
1 | ../../packages/signer-ethereum/README.md
--------------------------------------------------------------------------------
/docs/api/signer-solana.md:
--------------------------------------------------------------------------------
1 | ../../packages/signer-solana/README.md
--------------------------------------------------------------------------------
/docs/api/signer-substrate.md:
--------------------------------------------------------------------------------
1 | ../../packages/signer-substrate/README.md
--------------------------------------------------------------------------------
/docs/blog.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/examples-chat-next.md:
--------------------------------------------------------------------------------
1 | ../examples/chat-next/README.md
--------------------------------------------------------------------------------
/docs/examples-chat.md:
--------------------------------------------------------------------------------
1 | ../examples/chat/README.md
--------------------------------------------------------------------------------
/docs/examples-encrypted-chat.md:
--------------------------------------------------------------------------------
1 | ../examples/encrypted-chat/README.md
--------------------------------------------------------------------------------
/docs/examples-forum.md:
--------------------------------------------------------------------------------
1 | ../examples/forum/README.md
--------------------------------------------------------------------------------
/docs/examples-snake.md:
--------------------------------------------------------------------------------
1 | ../examples/snake/README.md
--------------------------------------------------------------------------------
/docs/public/contract_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/contract_code.png
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Black.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Black.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Bold.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Bold.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Light.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Light.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Medium.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Medium.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Regular.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Regular.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-SemiBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-SemiBold.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-SemiBold.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Thin.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-Thin.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-UltraBlack.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-UltraBlack.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-UltraBlack.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-UltraBlack.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-UltraLight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-UltraLight.woff
--------------------------------------------------------------------------------
/docs/public/fonts/Geist-UltraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/Geist-UltraLight.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Black.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Black.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Bold.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Bold.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Light.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Light.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Medium.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Medium.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Regular.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Regular.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-SemiBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-SemiBold.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-SemiBold.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Thin.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-Thin.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-UltraBlack.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-UltraBlack.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-UltraBlack.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-UltraBlack.woff2
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-UltraLight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-UltraLight.woff
--------------------------------------------------------------------------------
/docs/public/fonts/GeistMono-UltraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/fonts/GeistMono-UltraLight.woff2
--------------------------------------------------------------------------------
/docs/public/gossiplog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/gossiplog.png
--------------------------------------------------------------------------------
/docs/public/graphic_jellyfish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/graphic_jellyfish.png
--------------------------------------------------------------------------------
/docs/public/graphic_jellyfish_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/graphic_jellyfish_dark.png
--------------------------------------------------------------------------------
/docs/public/graphic_mainframe_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/graphic_mainframe_1.png
--------------------------------------------------------------------------------
/docs/public/graphic_mainframe_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/graphic_mainframe_2.png
--------------------------------------------------------------------------------
/docs/public/graphic_mainframe_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/graphic_mainframe_3.png
--------------------------------------------------------------------------------
/docs/public/graphic_mainframe_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/graphic_mainframe_4.png
--------------------------------------------------------------------------------
/docs/public/icon_computer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/icon_computer.png
--------------------------------------------------------------------------------
/docs/public/icon_computer_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/icon_computer_2.png
--------------------------------------------------------------------------------
/docs/public/icon_jellyfish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/icon_jellyfish.png
--------------------------------------------------------------------------------
/docs/public/icon_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/icon_logo.png
--------------------------------------------------------------------------------
/docs/public/icon_logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/icon_logo_dark.png
--------------------------------------------------------------------------------
/docs/public/icon_mainframe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/icon_mainframe.png
--------------------------------------------------------------------------------
/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/logo.png
--------------------------------------------------------------------------------
/docs/public/run_migrations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/docs/public/run_migrations.png
--------------------------------------------------------------------------------
/docs/ref/faq.md:
--------------------------------------------------------------------------------
1 | # FAQ
2 |
3 | ### What kind of applications is Canvas suitable for?
4 |
5 | Canvas is currently suitable for applications with public data.
6 |
7 | In particular, it's ideal for social applications, games, and any
8 | application that is aiming to run as an open interoperable protocol.
9 |
10 | We expect to support more classes of applications over time.
11 |
--------------------------------------------------------------------------------
/examples/chat-next/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/chat-next/app/favicon.ico
--------------------------------------------------------------------------------
/examples/chat-next/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | margin: 0 auto;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/chat-next/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next"
2 | import { Inter } from "next/font/google"
3 | import "./globals.css"
4 |
5 | const inter = Inter({ subsets: ["latin"] })
6 |
7 | export const metadata: Metadata = {
8 | title: "Canvas - Server Chat",
9 | description: "Canvas - Server Chat",
10 | }
11 |
12 | export default function RootLayout({
13 | children,
14 | }: Readonly<{
15 | children: React.ReactNode
16 | }>) {
17 | return (
18 |
19 |
20 |
{children}
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/examples/chat-next/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/chat-next/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | export default nextConfig
5 |
--------------------------------------------------------------------------------
/examples/chat-next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/chat-next-example",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "private": true,
6 | "scripts": {
7 | "dev": "npx tsx server.ts",
8 | "build": "next build",
9 | "start": "NODE_ENV=production tsx server.ts",
10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
11 | },
12 | "dependencies": {
13 | "@canvas-js/client": "0.16.1",
14 | "@canvas-js/core": "0.16.1",
15 | "@canvas-js/signer-ethereum": "0.16.1",
16 | "@canvas-js/utils": "1.0.0",
17 | "@ipld/dag-json": "^10.2.5",
18 | "express": "^5.1.0",
19 | "next": "^14.1.4",
20 | "react": "^18.3.1",
21 | "react-dom": "^18.3.1"
22 | },
23 | "devDependencies": {
24 | "@types/express": "^5.0.3",
25 | "@types/react": "^18.3.9",
26 | "@types/react-dom": "^18.3.0",
27 | "autoprefixer": "^10.4.19",
28 | "eslint": "^8.57.0",
29 | "eslint-config-next": "^14.1.4",
30 | "postcss": "^8.4.38",
31 | "tailwindcss": "^3.4.4",
32 | "ts-node": "^10.9.2",
33 | "tsx": "^4.20.3",
34 | "typescript": "~5.6.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/chat-next/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/chat-next/railway.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://railway.com/railway.schema.json",
3 | "build": {
4 | "builder": "NIXPACKS",
5 | "buildCommand": "npm run build && ./install-prod.sh && npm run build --workspace=examples/chat-next",
6 | "watchPatterns": [
7 | "/packages/network-explorer/**",
8 | "/packages/cli/**",
9 | "/packages/client/**",
10 | "/packages/core/**",
11 | "/packages/gossiplog/**",
12 | "/packages/hooks/**",
13 | "/packages/interfaces/**",
14 | "/packages/modeldb/**",
15 | "/packages/signer-*/**",
16 | "/examples/chat-next/**"
17 | ]
18 | },
19 | "deploy": {
20 | "startCommand": "npm run start --workspace examples/chat-next"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/chat-next/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "target": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [{ "name": "next" }]
18 | },
19 | "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
20 | "exclude": ["node_modules", ".next"]
21 | }
22 |
--------------------------------------------------------------------------------
/examples/chat-next/window.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | ethereum?: any
3 | }
4 |
--------------------------------------------------------------------------------
/examples/chat/.gitignore:
--------------------------------------------------------------------------------
1 | data/
2 |
--------------------------------------------------------------------------------
/examples/chat/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Canvas Chat
8 |
9 |
10 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/chat/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/chat/public/.well-known/farcaster.json:
--------------------------------------------------------------------------------
1 | {
2 | "accountAssociation": {
3 | "header": "eyJmaWQiOjE0NCwidHlwZSI6ImN1c3RvZHkiLCJrZXkiOiIweDJiYmFFZTg5MDBiYjE2NjRCMUM4M2EzQTRGMTQyY0RCRjIyMjRmYjgifQ",
4 | "payload": "eyJkb21haW4iOiJjaGF0LWV4YW1wbGUuY2FudmFzLnh5eiJ9",
5 | "signature": "MHhjMmU2NjE0ODQwZWM1MTlkODA3MThlMzdkNWI2ZTdhZGVlZDZhODgzZGM5MWU3YmYwZDM3NGNhYTc2MjM2YzhiM2Q1YWFiZjlhZjkxNWQ1NGY2ODgxOTE0N2YzZThlMDkxNTRlMmEwOTAyYWZhOWIwODJmN2UxOTI1MWE1NGU2NTFi"
6 | },
7 | "frame": {
8 | "version": "1",
9 | "name": "Example Frame",
10 | "iconUrl": "https://chat-example.canvas.xyz/icon.png",
11 | "homeUrl": "https://chat-example.canvas.xyz",
12 | "imageUrl": "https://chat-example.canvas.xyz/image.png",
13 | "buttonTitle": "Check this out",
14 | "splashImageUrl": "https://chat-example.canvas.xyz/splash.png",
15 | "splashBackgroundColor": "#eeccff"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/chat/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/chat/public/icon.png
--------------------------------------------------------------------------------
/examples/chat/public/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/chat/public/image.png
--------------------------------------------------------------------------------
/examples/chat/public/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/chat/public/splash.png
--------------------------------------------------------------------------------
/examples/chat/railway.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://railway.com/railway.schema.json",
3 | "build": {
4 | "builder": "NIXPACKS",
5 | "buildCommand": "npm run build && ./install-prod.sh && VITE_CANVAS_WS_URL=wss://chat-example.canvas.xyz npm run build --workspace=examples/chat",
6 | "watchPatterns": [
7 | "/packages/network-explorer/**",
8 | "/packages/cli/**",
9 | "/packages/core/**",
10 | "/packages/gossiplog/**",
11 | "/packages/hooks/**",
12 | "/packages/interfaces/**",
13 | "/packages/modeldb/**",
14 | "/packages/signer-*/**",
15 | "/examples/chat/**"
16 | ]
17 | },
18 | "deploy": {
19 | "startCommand": "canvas run examples/chat/src/contract.ts --static examples/chat/dist --network-explorer --admin 0x34C3A5ea06a3A67229fb21a7043243B0eB3e853f"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/chat/src/AppContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from "react"
2 |
3 | import type { SessionSigner } from "@canvas-js/interfaces"
4 | import { Canvas } from "@canvas-js/core"
5 |
6 | export type AppContext = {
7 | app: Canvas | null
8 |
9 | address: string | null
10 | setAddress: (address: string | null) => void
11 |
12 | sessionSigner: SessionSigner | null
13 | setSessionSigner: (signer: SessionSigner | null) => void
14 | }
15 |
16 | export const AppContext = createContext({
17 | app: null,
18 |
19 | address: null,
20 | setAddress: (address: string | null) => {
21 | throw new Error("AppContext.Provider not found")
22 | },
23 |
24 | sessionSigner: null,
25 | setSessionSigner: (signer) => {
26 | throw new Error("AppContext.Provider not found")
27 | },
28 | })
29 |
--------------------------------------------------------------------------------
/examples/chat/src/components/AddressView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export interface AddressViewProps {
4 | className?: string
5 | address: string
6 | }
7 |
8 | export const AddressView: React.FC = (props) => {
9 | const className = props.className ?? "text-sm"
10 | return (
11 |
12 | {/* {props.address.slice(0, 6)}…{props.address.slice(-4)} */}
13 | {props.address}
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/examples/chat/src/components/MultiaddrView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import type { PeerId } from "@libp2p/interface"
4 | import type { Multiaddr } from "@multiformats/multiaddr"
5 |
6 | export interface MultiaddrViewProps {
7 | addr: Multiaddr
8 | peerId?: PeerId
9 | }
10 |
11 | export const MultiaddrView: React.FC = (props) => {
12 | let address = props.addr.toString()
13 | if (props.peerId && address.endsWith(`/p2p/${props.peerId}`)) {
14 | address = address.slice(0, address.lastIndexOf("/p2p/"))
15 | }
16 |
17 | if (address.endsWith("/p2p-circuit/webrtc")) {
18 | return /webrtc
19 | } else {
20 | return {address}
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/chat/src/components/PeerIdView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import type { PeerId } from "@libp2p/interface"
4 |
5 | export interface PeerIdViewProps {
6 | className?: string
7 | peerId: PeerId
8 | }
9 |
10 | export const PeerIdView: React.FC = (props) => {
11 | const className = props.className ?? "text-sm"
12 | const id = props.peerId.toString()
13 | return (
14 |
15 | {/* {id.slice(0, 12)}…{id.slice(-4)} */}
16 | {id}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/examples/chat/src/contract.ts:
--------------------------------------------------------------------------------
1 | import type { ModelSchema } from "@canvas-js/core"
2 | import { Contract } from "@canvas-js/core/contract"
3 |
4 | export default class Chat extends Contract {
5 | static topic = "chat-example.canvas.xyz"
6 |
7 | static models = {
8 | message: {
9 | id: "primary",
10 | address: "string",
11 | content: "string",
12 | timestamp: "integer",
13 | $indexes: ["address", "timestamp"],
14 | },
15 | } satisfies ModelSchema
16 |
17 | async createMessage(arg: string | { content: string }) {
18 | const { id, address, timestamp, db } = this
19 | const content = typeof arg === "string" ? arg : arg.content
20 | await db.set("message", { id, address, content, timestamp })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/chat/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 |
4 | import "../styles.css"
5 |
6 | import { App } from "./App.js"
7 |
8 | const root = ReactDOM.createRoot(document.getElementById("root")!)
9 |
10 | root.render(
11 |
12 |
13 | ,
14 | )
15 |
--------------------------------------------------------------------------------
/examples/chat/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body,
7 | #root {
8 | margin: 0;
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | main {
14 | height: 100%;
15 | max-width: 960px;
16 | padding: 2em;
17 | }
18 |
--------------------------------------------------------------------------------
/examples/chat/tailwind.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | content: ["./src/**/*.{html,js,tsx}"],
3 | plugins: [],
4 | }
5 |
--------------------------------------------------------------------------------
/examples/chat/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "types": ["vite/client"],
6 | "rootDir": "src",
7 | "outDir": "lib",
8 | "esModuleInterop": true,
9 | "jsx": "react"
10 | },
11 | "references": [
12 | { "path": "../../packages/core" },
13 | { "path": "../../packages/signer-atp" },
14 | { "path": "../../packages/signer-cosmos" },
15 | { "path": "../../packages/signer-ethereum" },
16 | { "path": "../../packages/signer-ethereum-viem" },
17 | { "path": "../../packages/signer-solana" },
18 | { "path": "../../packages/signer-substrate" },
19 | { "path": "../../packages/gossiplog" },
20 | { "path": "../../packages/hooks" },
21 | { "path": "../../packages/interfaces" },
22 | { "path": "../../packages/modeldb" }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/examples/chat/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import wasm from "vite-plugin-wasm"
3 | import { nodePolyfills } from "vite-plugin-node-polyfills"
4 |
5 | export default defineConfig({
6 | // ...other config settings
7 | plugins: [nodePolyfills({ globals: { Buffer: true } }), wasm()],
8 | server: {
9 | headers: {
10 | "Cross-Origin-Opener-Policy": "same-origin",
11 | "Cross-Origin-Embedder-Policy": "require-corp",
12 | },
13 | },
14 | build: {
15 | minify: false,
16 | target: "es2022",
17 | },
18 | optimizeDeps: {
19 | exclude: ["@sqlite.org/sqlite-wasm", "quickjs-emscripten"],
20 | esbuildOptions: {
21 | // Node.js global to browser globalThis
22 | define: {
23 | global: "globalThis",
24 | },
25 | },
26 | },
27 | publicDir: 'public'
28 | })
29 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"],
5 | ignorePatterns: ["dist", ".eslintrc.cjs"],
6 | parser: "@typescript-eslint/parser",
7 | plugins: ["react-refresh"],
8 | rules: {
9 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/.gitignore:
--------------------------------------------------------------------------------
1 | data/
2 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/railway.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://railway.com/railway.schema.json",
3 | "build": {
4 | "builder": "NIXPACKS",
5 | "buildCommand": "npm run build && ./install-prod.sh && VITE_CANVAS_WS_URL=wss://encrypted-chat-example.canvas.xyz npm run build --workspace=examples/encrypted-chat",
6 | "watchPatterns": [
7 | "/packages/network-explorer/**",
8 | "/packages/cli/**",
9 | "/packages/core/**",
10 | "/packages/gossiplog/**",
11 | "/packages/hooks/**",
12 | "/packages/interfaces/**",
13 | "/packages/modeldb/**",
14 | "/packages/signer-ethereum/**",
15 | "/examples/forum/**"
16 | ]
17 | },
18 | "deploy": {
19 | "startCommand": "canvas run examples/encrypted-chat/src/contract.ts --static examples/encrypted-chat/dist --network-explorer --admin 0x34C3A5ea06a3A67229fb21a7043243B0eB3e853f"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/src/components/privkeys.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react"
2 | import { ethers } from "ethers"
3 |
4 | export const usePrivkey = (storageId: string) => {
5 | const [privkey, setPrivkey] = useState()
6 | useEffect(() => {
7 | let wallet
8 | const privkey = localStorage.getItem(storageId)
9 | if (privkey) {
10 | try {
11 | wallet = new ethers.Wallet(privkey)
12 | } catch (err) {
13 | wallet = ethers.Wallet.createRandom()
14 | }
15 | } else {
16 | wallet = ethers.Wallet.createRandom()
17 | }
18 | localStorage.setItem(storageId, wallet.privateKey)
19 | setPrivkey(wallet.privateKey)
20 | }, [storageId])
21 |
22 | return privkey
23 | }
24 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import App from "./App.tsx"
4 | import "./index.css"
5 |
6 | ReactDOM.createRoot(document.getElementById("root")!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"]
24 | }
25 |
--------------------------------------------------------------------------------
/examples/encrypted-chat/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import wasm from "vite-plugin-wasm"
3 | import { nodePolyfills } from "vite-plugin-node-polyfills"
4 |
5 | export default defineConfig({
6 | // ...other config settings
7 | plugins: [nodePolyfills({ globals: { Buffer: true } }), wasm()],
8 | server: {
9 | headers: {
10 | "Cross-Origin-Opener-Policy": "same-origin",
11 | "Cross-Origin-Embedder-Policy": "require-corp",
12 | },
13 | },
14 | build: {
15 | minify: false,
16 | target: "es2022",
17 | },
18 | optimizeDeps: {
19 | exclude: ["@sqlite.org/sqlite-wasm", "quickjs-emscripten"],
20 | esbuildOptions: {
21 | // Node.js global to browser globalThis
22 | define: {
23 | global: "globalThis",
24 | },
25 | },
26 | },
27 | })
28 |
--------------------------------------------------------------------------------
/examples/forum/.gitignore:
--------------------------------------------------------------------------------
1 | data/
2 |
--------------------------------------------------------------------------------
/examples/forum/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Forum Example
8 |
9 |
10 |
11 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/forum/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/forum/public/.well-known/farcaster.json:
--------------------------------------------------------------------------------
1 | {
2 | "accountAssociation": {
3 | "header": "eyJmaWQiOjE0NCwidHlwZSI6ImN1c3RvZHkiLCJrZXkiOiIweDJiYmFFZTg5MDBiYjE2NjRCMUM4M2EzQTRGMTQyY0RCRjIyMjRmYjgifQ",
4 | "payload": "eyJkb21haW4iOiJmb3J1bS1leGFtcGxlLmNhbnZhcy54eXoifQ",
5 | "signature": "MHhiZmVkNWJmOWIxZDEwYTY4NTkwOWZjNmIxODJjZTQ0NzUzNTUyY2RlNTcyYmU5OWEzYzcwOTUxNTI4YTI4MTllM2U4YjI3OGJjYmFhMWIyNDYxN2E3NDY5MjMyZDVhYmJlOTM3NTA3YzQ3ODZhOGY3ODk4ZWY1NjM4ZGUyZDI5MDFi"
6 | },
7 | "frame": {
8 | "version": "1",
9 | "name": "Example Frame",
10 | "iconUrl": "https://forum-example.canvas.xyz/icon.png",
11 | "homeUrl": "https://forum-example.canvas.xyz",
12 | "imageUrl": "https://forum-example.canvas.xyz/image.png",
13 | "buttonTitle": "Check this out",
14 | "splashImageUrl": "https://forum-example.canvas.xyz/splash.png",
15 | "splashBackgroundColor": "#eeccff",
16 | "webhookUrl": "https://forum-example.canvas.xyz/api/webhook"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/forum/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/forum/public/icon.png
--------------------------------------------------------------------------------
/examples/forum/public/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/forum/public/image.png
--------------------------------------------------------------------------------
/examples/forum/public/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/forum/public/splash.png
--------------------------------------------------------------------------------
/examples/forum/railway.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://railway.com/railway.schema.json",
3 | "build": {
4 | "builder": "NIXPACKS",
5 | "buildCommand": "npm run build && ./install-prod.sh && VITE_CANVAS_WS_URL=wss://forum-example.canvas.xyz npm run build --workspace=examples/forum",
6 | "watchPatterns": [
7 | "/packages/network-explorer/**",
8 | "/packages/cli/**",
9 | "/packages/core/**",
10 | "/packages/gossiplog/**",
11 | "/packages/hooks/**",
12 | "/packages/interfaces/**",
13 | "/packages/modeldb/**",
14 | "/packages/signer-ethereum/**",
15 | "/examples/forum/**"
16 | ]
17 | },
18 | "deploy": {
19 | "startCommand": "canvas run examples/forum/src/contract.ts --static examples/forum/dist --network-explorer --admin 0x34C3A5ea06a3A67229fb21a7043243B0eB3e853f"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/forum/src/AppContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from "react"
2 |
3 | import type { SessionSigner } from "@canvas-js/interfaces"
4 | import { Canvas } from "@canvas-js/core"
5 |
6 | export type AppContext = {
7 | app: Canvas | null
8 | }
9 |
10 | export const AppContext = createContext({
11 | app: null,
12 | })
13 |
--------------------------------------------------------------------------------
/examples/forum/src/MessageComposer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useContext, useState } from "react"
2 |
3 | import { AppContext } from "./AppContext.js"
4 |
5 | export interface MessageComposerProps {}
6 |
7 | export const MessageComposer: React.FC = ({}) => {
8 | const { app } = useContext(AppContext)
9 |
10 | const [value, setValue] = useState("")
11 |
12 | const handleKeyDown = useCallback(
13 | async (event: React.KeyboardEvent) => {
14 | if (event.key === "Enter" && app !== null) {
15 | try {
16 | const { id } = await app.actions.createMessage(value)
17 | setValue("")
18 | } catch (err) {
19 | console.log(err)
20 | console.error(err)
21 | }
22 | }
23 | },
24 | [app, value],
25 | )
26 |
27 | if (app === null) {
28 | return null
29 | }
30 |
31 | return (
32 | setValue(event.target.value)}
38 | onKeyDown={handleKeyDown}
39 | placeholder="Send a message..."
40 | />
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/examples/forum/src/contract.ts:
--------------------------------------------------------------------------------
1 | import { ModelSchema } from "@canvas-js/core"
2 | import { Contract } from "@canvas-js/core/contract"
3 |
4 | export default class Forum extends Contract {
5 | static topic = "forum-example.canvas.xyz"
6 |
7 | static models = {
8 | posts: {
9 | id: "primary",
10 | title: "string",
11 | text: "string",
12 | author: "string",
13 | timestamp: "number",
14 | $indexes: ["timestamp"],
15 | },
16 | } satisfies ModelSchema
17 |
18 | async createPost(title: string, text: string) {
19 | await this.db.set("posts", { id: this.id, title, text, author: this.did, timestamp: this.timestamp })
20 | }
21 |
22 | async deletePost(id: string) {
23 | if (this.address !== "0x34C3A5ea06a3A67229fb21a7043243B0eB3e853f") throw new Error()
24 | await this.db.delete("posts", id)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/forum/src/index.tsx:
--------------------------------------------------------------------------------
1 | import "../styles.css"
2 |
3 | import React from "react"
4 | import ReactDOM from "react-dom/client"
5 |
6 | import { Canvas } from "@canvas-js/core"
7 | import { AuthProvider } from "@canvas-js/hooks"
8 |
9 | import Forum from "./contract.js"
10 | import Layout from "./Layout.js"
11 |
12 | export const ADMIN_DID = "did:pkh:eip155:1:0x34C3A5ea06a3A67229fb21a7043243B0eB3e853f"
13 |
14 | export type AppT = Canvas
15 |
16 | const root = ReactDOM.createRoot(document.getElementById("root")!)
17 |
18 | root.render(
19 |
20 |
21 |
22 |
23 | ,
24 | )
25 |
--------------------------------------------------------------------------------
/examples/forum/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body,
7 | #root {
8 | margin: 0;
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | #root #loading {
14 | width: 100%;
15 | text-align: center;
16 | margin: 5rem 0;
17 | color: #eee;
18 | }
19 |
20 | html, body {
21 | background: rgb(37, 37, 41);
22 | }
23 |
24 | main {
25 | height: 100%;
26 | width: 100%;
27 | }
28 |
29 | .fc-authkit-signin-button button {
30 | width: 100%;
31 | align-items: center;
32 | justify-content: center;
33 | font-size: 100%;
34 | padding: 10px 21px;
35 | font-weight: 500;
36 | border-radius: 5px;
37 | }
38 | .fc-authkit-signin-button button svg {
39 | height: 17px;
40 | }
41 | .fc-authkit-signin-button button span:after {
42 | content: " with Farcaster";
43 | }
44 |
45 | a {
46 | color: #5284f0;
47 | }
48 | p {
49 | margin: 4px 0;
50 | }
51 |
--------------------------------------------------------------------------------
/examples/forum/tailwind.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | content: ["./src/**/*.{html,js,tsx}"],
3 | plugins: [],
4 | theme: {
5 | extend: {
6 | colors: {
7 | gray: {
8 | 50: "#f9f9f9",
9 | 100: "#efefef",
10 | 200: "#dfdfdf",
11 | 300: "#cccccc",
12 | 400: "#a8a8a8",
13 | 500: "#8a8a8a",
14 | 600: "#666666",
15 | 700: "#4d4d4d",
16 | 800: "#333333",
17 | 900: "#1a1a1a",
18 | 950: "#0d0d0d",
19 | },
20 | },
21 | },
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/examples/forum/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "baseUrl": "../../",
6 | "paths": {
7 | "@canvas-js/modeldb": ["packages/modeldb/lib/index.d.ts"]
8 | },
9 | "types": ["vite/client"],
10 | "rootDir": "src",
11 | "outDir": "lib",
12 | "esModuleInterop": true,
13 | "jsx": "react"
14 | },
15 | "references": [
16 | { "path": "../../packages/core" },
17 | { "path": "../../packages/signer-ethereum" },
18 | { "path": "../../packages/gossiplog" },
19 | { "path": "../../packages/hooks" },
20 | { "path": "../../packages/interfaces" },
21 | { "path": "../../packages/modeldb" }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/examples/forum/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import wasm from "vite-plugin-wasm"
3 | import { nodePolyfills } from "vite-plugin-node-polyfills"
4 |
5 | export default defineConfig({
6 | // ...other config settings
7 | plugins: [nodePolyfills({ globals: { Buffer: true } }), wasm()],
8 | server: {
9 | headers: {
10 | "Cross-Origin-Opener-Policy": "same-origin",
11 | "Cross-Origin-Embedder-Policy": "require-corp",
12 | },
13 | },
14 | build: {
15 | minify: false,
16 | target: "es2022",
17 | emptyOutDir: false,
18 | },
19 | optimizeDeps: {
20 | exclude: ["@sqlite.org/sqlite-wasm", "quickjs-emscripten"],
21 | esbuildOptions: {
22 | // Node.js global to browser globalThis
23 | define: {
24 | global: "globalThis",
25 | },
26 | },
27 | },
28 | publicDir: "public",
29 | })
30 |
--------------------------------------------------------------------------------
/examples/refs/.gitignore:
--------------------------------------------------------------------------------
1 | data/
2 |
--------------------------------------------------------------------------------
/examples/refs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Refs Example
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/refs/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/refs/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/refs/public/icon.png
--------------------------------------------------------------------------------
/examples/refs/public/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/refs/public/image.png
--------------------------------------------------------------------------------
/examples/refs/public/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/refs/public/splash.png
--------------------------------------------------------------------------------
/examples/refs/railway.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://railway.com/railway.schema.json",
3 | "build": {
4 | "builder": "NIXPACKS",
5 | "buildCommand": "npm run build && ./install-prod.sh && VITE_CANVAS_WS_URL=wss://forum-example.canvas.xyz npm run build --workspace=examples/forum",
6 | "watchPatterns": [
7 | "/packages/network-explorer/**",
8 | "/packages/cli/**",
9 | "/packages/core/**",
10 | "/packages/gossiplog/**",
11 | "/packages/hooks/**",
12 | "/packages/interfaces/**",
13 | "/packages/modeldb/**",
14 | "/packages/signer-ethereum/**",
15 | "/examples/forum/**"
16 | ]
17 | },
18 | "deploy": {
19 | "startCommand": "canvas run examples/forum/src/contract.ts --static examples/forum/dist --network-explorer --admin 0x34C3A5ea06a3A67229fb21a7043243B0eB3e853f"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/refs/src/AppContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from "react"
2 |
3 | import type { SessionSigner } from "@canvas-js/interfaces"
4 | import { Canvas } from "@canvas-js/core"
5 |
6 | export type AppContext = {
7 | app: Canvas | null
8 | }
9 |
10 | export const AppContext = createContext({
11 | app: null,
12 | })
13 |
--------------------------------------------------------------------------------
/examples/refs/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body,
7 | #root {
8 | margin: 0;
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | #root #loading {
14 | width: 100%;
15 | text-align: center;
16 | margin: 5rem 0;
17 | color: #eee;
18 | }
19 |
20 | html, body {
21 | background: rgb(37, 37, 41);
22 | color: #eee;
23 | }
24 |
--------------------------------------------------------------------------------
/examples/refs/tailwind.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | content: ["./src/**/*.{html,js,tsx}"],
3 | plugins: [],
4 | theme: {
5 | extend: {
6 | colors: {
7 | gray: {
8 | 50: "#f9f9f9",
9 | 100: "#efefef",
10 | 200: "#dfdfdf",
11 | 300: "#cccccc",
12 | 400: "#a8a8a8",
13 | 500: "#8a8a8a",
14 | 600: "#666666",
15 | 700: "#4d4d4d",
16 | 800: "#333333",
17 | 900: "#1a1a1a",
18 | 950: "#0d0d0d",
19 | },
20 | },
21 | },
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/examples/refs/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "module": "Node16",
6 | "moduleResolution": "Node16",
7 | "rootDir": ".",
8 | "outDir": "lib"
9 | },
10 | "references": [
11 | { "path": "../." },
12 | { "path": "../../../packages/core" },
13 | { "path": "../../../packages/gossiplog" },
14 | { "path": "../../../packages/hooks" },
15 | { "path": "../../../packages/interfaces" },
16 | { "path": "../../../packages/signatures" },
17 | { "path": "../../../packages/signer-ethereum" }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/refs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "baseUrl": "../../",
6 | "paths": {
7 | "@canvas-js/modeldb": ["packages/modeldb/lib/index.d.ts"]
8 | },
9 | "types": ["vite/client"],
10 | "rootDir": "src",
11 | "outDir": "lib",
12 | "esModuleInterop": true,
13 | "jsx": "react"
14 | },
15 | "references": [
16 | { "path": "../../packages/core" },
17 | { "path": "../../packages/signer-ethereum" },
18 | { "path": "../../packages/gossiplog" },
19 | { "path": "../../packages/hooks" },
20 | { "path": "../../packages/interfaces" },
21 | { "path": "../../packages/modeldb" }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/examples/refs/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import wasm from "vite-plugin-wasm"
3 | import { nodePolyfills } from "vite-plugin-node-polyfills"
4 |
5 | export default defineConfig({
6 | // ...other config settings
7 | plugins: [nodePolyfills({ globals: { Buffer: true } }), wasm()],
8 | server: {
9 | headers: {
10 | "Cross-Origin-Opener-Policy": "same-origin",
11 | "Cross-Origin-Embedder-Policy": "require-corp",
12 | },
13 | },
14 | build: {
15 | minify: false,
16 | target: "es2022",
17 | emptyOutDir: false,
18 | },
19 | optimizeDeps: {
20 | exclude: ["@sqlite.org/sqlite-wasm", "quickjs-emscripten"],
21 | esbuildOptions: {
22 | // Node.js global to browser globalThis
23 | define: {
24 | global: "globalThis",
25 | },
26 | },
27 | },
28 | publicDir: "public",
29 | })
30 |
--------------------------------------------------------------------------------
/examples/starter/.gitignore:
--------------------------------------------------------------------------------
1 | data/
2 |
--------------------------------------------------------------------------------
/examples/starter/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Starter Example
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/starter/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/starter/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/starter/public/icon.png
--------------------------------------------------------------------------------
/examples/starter/public/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/starter/public/image.png
--------------------------------------------------------------------------------
/examples/starter/public/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/examples/starter/public/splash.png
--------------------------------------------------------------------------------
/examples/starter/railway.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://railway.com/railway.schema.json",
3 | "build": {
4 | "builder": "NIXPACKS",
5 | "buildCommand": "npm run build && ./install-prod.sh && VITE_CANVAS_WS_URL=wss://forum-example.canvas.xyz npm run build --workspace=examples/forum",
6 | "watchPatterns": [
7 | "/packages/network-explorer/**",
8 | "/packages/cli/**",
9 | "/packages/core/**",
10 | "/packages/gossiplog/**",
11 | "/packages/hooks/**",
12 | "/packages/interfaces/**",
13 | "/packages/modeldb/**",
14 | "/packages/signer-ethereum/**",
15 | "/examples/forum/**"
16 | ]
17 | },
18 | "deploy": {
19 | "startCommand": "canvas run examples/forum/src/contract.ts --static examples/forum/dist --network-explorer --admin 0x34C3A5ea06a3A67229fb21a7043243B0eB3e853f"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/starter/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Canvas } from "@canvas-js/core"
3 | import { useLiveQuery } from "@canvas-js/hooks"
4 | import Counter from "./contract.js"
5 |
6 | export const App: React.FC<{ app: Canvas }> = ({ app }) => {
7 | const [row] = useLiveQuery(app, "counter") ?? []
8 |
9 | return (
10 |
11 |
Counter
12 |
13 |
14 |
15 | Count:
16 | {row?.count ?? 0}
17 |
18 |
19 |
20 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/examples/starter/src/AppContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from "react"
2 |
3 | import type { SessionSigner } from "@canvas-js/interfaces"
4 | import { Canvas } from "@canvas-js/core"
5 |
6 | export type AppContext = {
7 | app: Canvas | null
8 | }
9 |
10 | export const AppContext = createContext({
11 | app: null,
12 | })
13 |
--------------------------------------------------------------------------------
/examples/starter/src/contract.ts:
--------------------------------------------------------------------------------
1 | import type { ModelSchema } from "@canvas-js/core"
2 | import { Contract } from "@canvas-js/core/contract"
3 |
4 | export default class Counter extends Contract {
5 | static topic = "counter.canvas.xyz"
6 |
7 | static models = {
8 | counter: {
9 | id: "primary",
10 | count: "number",
11 | },
12 | } satisfies ModelSchema
13 |
14 | async increment() {
15 | await this.db.transaction(async () => {
16 | const counter = await this.db.get("counter", "0")
17 | const count = counter ? counter.count : 0
18 | await this.db.set("counter", { id: "0", count: count + 1 })
19 | })
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/starter/src/index.tsx:
--------------------------------------------------------------------------------
1 | import "../styles.css"
2 |
3 | import React from "react"
4 | import ReactDOM from "react-dom/client"
5 |
6 | import { AuthProvider } from "@canvas-js/hooks"
7 | import Layout from "./Layout.js"
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("root")!)
10 |
11 | root.render(
12 |
13 |
14 |
15 |
16 | ,
17 | )
18 |
--------------------------------------------------------------------------------
/examples/starter/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body,
7 | #root {
8 | margin: 0;
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | #root #loading {
14 | width: 100%;
15 | text-align: center;
16 | margin: 5rem 0;
17 | color: #eee;
18 | }
19 |
20 | html, body {
21 | background: rgb(37, 37, 41);
22 | color: #eee;
23 | }
24 |
--------------------------------------------------------------------------------
/examples/starter/tailwind.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | content: ["./src/**/*.{html,js,tsx}"],
3 | plugins: [],
4 | theme: {
5 | extend: {
6 | colors: {
7 | gray: {
8 | 50: "#f9f9f9",
9 | 100: "#efefef",
10 | 200: "#dfdfdf",
11 | 300: "#cccccc",
12 | 400: "#a8a8a8",
13 | 500: "#8a8a8a",
14 | 600: "#666666",
15 | 700: "#4d4d4d",
16 | 800: "#333333",
17 | 900: "#1a1a1a",
18 | 950: "#0d0d0d",
19 | },
20 | },
21 | },
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/examples/starter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "baseUrl": "../../",
6 | "paths": {
7 | "@canvas-js/modeldb": ["packages/modeldb/lib/index.d.ts"]
8 | },
9 | "types": ["vite/client"],
10 | "rootDir": "src",
11 | "outDir": "lib",
12 | "esModuleInterop": true,
13 | "jsx": "react"
14 | },
15 | "references": [
16 | { "path": "../../packages/core" },
17 | { "path": "../../packages/signer-ethereum" },
18 | { "path": "../../packages/gossiplog" },
19 | { "path": "../../packages/hooks" },
20 | { "path": "../../packages/interfaces" },
21 | { "path": "../../packages/modeldb" }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/examples/starter/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import wasm from "vite-plugin-wasm"
3 | import { nodePolyfills } from "vite-plugin-node-polyfills"
4 |
5 | export default defineConfig({
6 | // ...other config settings
7 | plugins: [nodePolyfills({ globals: { Buffer: true } }), wasm()],
8 | server: {
9 | headers: {
10 | "Cross-Origin-Opener-Policy": "same-origin",
11 | "Cross-Origin-Embedder-Policy": "require-corp",
12 | },
13 | },
14 | build: {
15 | minify: false,
16 | target: "es2022",
17 | emptyOutDir: false,
18 | },
19 | optimizeDeps: {
20 | exclude: ["@sqlite.org/sqlite-wasm", "quickjs-emscripten"],
21 | esbuildOptions: {
22 | // Node.js global to browser globalThis
23 | define: {
24 | global: "globalThis",
25 | },
26 | },
27 | },
28 | publicDir: "public",
29 | })
30 |
--------------------------------------------------------------------------------
/install-prod.sh:
--------------------------------------------------------------------------------
1 | INSTALL_PATH=node_modules/.bin/canvas
2 | touch ${INSTALL_PATH}
3 | echo "#!/usr/bin/env sh" >> ${INSTALL_PATH}
4 | echo "node ${PWD}/packages/cli/dist/index.js \$@" >> ${INSTALL_PATH}
5 | chmod +x ${INSTALL_PATH}
6 | echo "Done! Installed CLI to" $INSTALL_PATH
7 |
8 | PATH=$INSTALL_PATH:$PATH
9 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | INSTALL_PATH=$(npm config get prefix)/bin/canvas
4 |
5 | echo "Installing the local development build of the Canvas CLI to /usr/bin/dev."
6 |
7 | if test -f "$INSTALL_PATH"; then
8 | read -p "Already installed. Overwrite any past install? [y/N] " -n 1 -r
9 | if [[ $REPLY =~ ^[Yy]$ ]]
10 | then
11 | # Overwrite any past install
12 | echo "#!/usr/bin/env sh" > ${INSTALL_PATH}
13 | else
14 | echo
15 | echo "Aborting, ok!"
16 | exit 1
17 | fi
18 | else
19 | # Create a new install
20 | touch ${INSTALL_PATH}
21 | echo "#!/usr/bin/env sh" >> ${INSTALL_PATH}
22 | fi
23 | echo "node ${PWD}/packages/cli/dist/index.js \$@" >> ${INSTALL_PATH}
24 | chmod +x ${INSTALL_PATH}
25 | echo
26 | echo "Done!"
27 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22-alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY lib lib
6 | COPY package.json package.json
7 | RUN npm install --omit-dev
8 |
9 | CMD ["npm", "run", "start"]
10 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/README.md:
--------------------------------------------------------------------------------
1 | # @canvas-js/bootstrap-peer
2 |
3 | The public multiaddrs for the mainnet bootstrap peers are
4 |
5 | ```
6 | - /dns4/canvas-bootstrap-p0.fly.dev/tcp/443/wss/p2p/12D3KooWB3X5KnsTMknFLqxMf2979zddpwCWZ5ix1FosV5sFC4py
7 | - /dns4/canvas-bootstrap-p1.fly.dev/tcp/443/wss/p2p/12D3KooWCSsK4VGWx4gA2FiUKQFEHyLrL6bf4xAJm6kqB2gkB59w
8 | - /dns4/canvas-bootstrap-p2.fly.dev/tcp/443/wss/p2p/12D3KooWHWLKtWifVTMwJQRG6jcVCV3kLQ49GCB4DgevLTnKWor3
9 | ```
10 |
11 | ## Deploy
12 |
13 | Deploy "mainnet":
14 |
15 | ```
16 | $ ./deploy-mainnet.sh
17 | ```
18 |
19 | ## HTTP API
20 |
21 | These routes are only accessible from the internal Canvas wireguard VPN.
22 |
23 | ```
24 | - GET /api/connections
25 | - GET /api/registrations
26 | - GET /metrics
27 | ```
28 |
29 | e.g.
30 |
31 | ```
32 | $ curl http://canvas-bootstrap-p0.internal:8000/api/registrations | jq
33 | ```
34 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/create-peer-id.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { generateKeyPair, privateKeyToProtobuf } from "@libp2p/crypto/keys"
4 | import { peerIdFromPublicKey } from "@libp2p/peer-id"
5 |
6 | const privateKey = await generateKeyPair("Ed25519")
7 | const peerId = peerIdFromPublicKey(privateKey.publicKey)
8 | console.log(`# ${peerId}`)
9 | console.log(`LIBP2P_PRIVATE_KEY=${Buffer.from(privateKeyToProtobuf(privateKey)).toString("base64")}`)
10 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/deploy-mainnet.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | fly deploy --build-arg NODE_ENV=production --remote-only -a canvas-bootstrap-p0 -c mainnet/p0.fly.toml
3 | fly deploy --build-arg NODE_ENV=production --remote-only -a canvas-bootstrap-p1 -c mainnet/p1.fly.toml
4 | fly deploy --build-arg NODE_ENV=production --remote-only -a canvas-bootstrap-p2 -c mainnet/p2.fly.toml
5 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/deploy-testnet.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | fly deploy --build-arg NODE_ENV=production --remote-only -a canvas-testnet-p0 -c testnet/p0.fly.toml
3 | fly deploy --build-arg NODE_ENV=production --remote-only -a canvas-testnet-p1 -c testnet/p1.fly.toml
4 | fly deploy --build-arg NODE_ENV=production --remote-only -a canvas-testnet-p2 -c testnet/p2.fly.toml
5 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/mainnet/p0.fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-bootstrap-p0'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | ANNOUNCE = '/dns4/canvas-bootstrap-p0.fly.dev/tcp/443/wss'
10 | DEBUG = 'canvas:rendezvous:server,libp2p:*'
11 | LISTEN = '/ip4/0.0.0.0/tcp/8080/ws'
12 | DATABASE_PATH = '/data/db.sqlite'
13 | PORT = '8000'
14 |
15 | [mounts]
16 | source = "data"
17 | destination = "/data"
18 | initial_size = "1gb"
19 |
20 | [[services]]
21 | protocol = 'tcp'
22 | internal_port = 8080
23 | processes = ['app']
24 |
25 | [[services.ports]]
26 | port = 443
27 | handlers = ['tls', 'http']
28 |
29 | [services.concurrency]
30 | type = 'connections'
31 | hard_limit = 1024
32 | soft_limit = 512
33 |
34 | [[vm]]
35 | memory = '2gb'
36 | cpu_kind = 'shared'
37 | cpus = 2
38 |
39 | [[metrics]]
40 | port = 8000
41 | path = '/metrics'
42 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/mainnet/p1.fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-bootstrap-p1'
2 | primary_region = 'ams'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | ANNOUNCE = '/dns4/canvas-bootstrap-p1.fly.dev/tcp/443/wss'
10 | DEBUG = 'canvas:rendezvous:server,libp2p:*'
11 | LISTEN = '/ip4/0.0.0.0/tcp/8080/ws'
12 | DATABASE_PATH = '/data/db.sqlite'
13 | PORT = '8000'
14 |
15 | [mounts]
16 | source = "data"
17 | destination = "/data"
18 | initial_size = "1gb"
19 |
20 | [[services]]
21 | protocol = 'tcp'
22 | internal_port = 8080
23 | processes = ['app']
24 |
25 | [[services.ports]]
26 | port = 443
27 | handlers = ['tls', 'http']
28 |
29 | [services.concurrency]
30 | type = 'connections'
31 | hard_limit = 1024
32 | soft_limit = 512
33 |
34 | [[vm]]
35 | memory = '2gb'
36 | cpu_kind = 'shared'
37 | cpus = 2
38 |
39 | [[metrics]]
40 | port = 8000
41 | path = '/metrics'
42 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/mainnet/p2.fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-bootstrap-p2'
2 | primary_region = 'lax'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | ANNOUNCE = '/dns4/canvas-bootstrap-p2.fly.dev/tcp/443/wss'
10 | DEBUG = 'canvas:rendezvous:server,libp2p:*'
11 | LISTEN = '/ip4/0.0.0.0/tcp/8080/ws'
12 | DATABASE_PATH = '/data/db.sqlite'
13 | PORT = '8000'
14 |
15 | [mounts]
16 | source = "data"
17 | destination = "/data"
18 | initial_size = "1gb"
19 |
20 | [[services]]
21 | protocol = 'tcp'
22 | internal_port = 8080
23 | processes = ['app']
24 |
25 | [[services.ports]]
26 | port = 443
27 | handlers = ['tls', 'http']
28 |
29 | [services.concurrency]
30 | type = 'connections'
31 | hard_limit = 1024
32 | soft_limit = 512
33 |
34 | [[vm]]
35 | memory = '2gb'
36 | cpu_kind = 'shared'
37 | cpus = 2
38 |
39 | [[metrics]]
40 | port = 8000
41 | path = '/metrics'
42 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/monitor.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | SESSIONNAME="canvas-bootstrap-logs"
4 | tmux has-session -t $SESSIONNAME &> /dev/null
5 |
6 | if [ $? != 0 ]
7 | then
8 | tmux new-session -s $SESSIONNAME -n $SESSIONNAME -d
9 | tmux split-window -v
10 | tmux split-window -v
11 | tmux select-layout even-vertical
12 | tmux send-keys -t $SESSIONNAME:0.0 "fly logs -a canvas-bootstrap-p0" C-m
13 | tmux send-keys -t $SESSIONNAME:0.1 "fly logs -a canvas-bootstrap-p1" C-m
14 | tmux send-keys -t $SESSIONNAME:0.2 "fly logs -a canvas-bootstrap-p2" C-m
15 | fi
16 |
17 | tmux attach -t $SESSIONNAME
18 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/testnet/p0.fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-testnet-p0'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | ANNOUNCE = '/dns6/canvas-testnet-p0.internal/tcp/8080/ws'
10 | DEBUG = 'canvas:*,libp2p:*'
11 | LISTEN = '/ip6/::/tcp/8080/ws'
12 | PORT = '8000'
13 |
14 | [[vm]]
15 | memory = '1gb'
16 | cpu_kind = 'shared'
17 | cpus = 1
18 |
19 | [[metrics]]
20 | port = 8000
21 | path = '/metrics'
22 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/testnet/p1.fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-testnet-p1'
2 | primary_region = 'ams'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | ANNOUNCE = '/dns6/canvas-testnet-p1.internal/tcp/8080/ws'
10 | DEBUG = 'canvas:*,libp2p:*'
11 | LISTEN = '/ip6/::/tcp/8080/ws'
12 | PORT = '8000'
13 |
14 | [[vm]]
15 | memory = '1gb'
16 | cpu_kind = 'shared'
17 | cpus = 1
18 |
19 | [[metrics]]
20 | port = 8000
21 | path = '/metrics'
22 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/testnet/p2.fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-testnet-p2'
2 | primary_region = 'lax'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | ANNOUNCE = '/dns6/canvas-testnet-p2.internal/tcp/8080/ws'
10 | DEBUG = 'canvas:*,libp2p:*'
11 | LISTEN = '/ip6/::/tcp/8080/ws'
12 | PORT = '8000'
13 |
14 | [[vm]]
15 | memory = '1gb'
16 | cpu_kind = 'shared'
17 | cpus = 1
18 |
19 | [[metrics]]
20 | port = 8000
21 | path = '/metrics'
22 |
--------------------------------------------------------------------------------
/packages/bootstrap-peer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/index.ts:
--------------------------------------------------------------------------------
1 | import type { CommandModule } from "yargs"
2 |
3 | import * as info from "./info.js"
4 | import * as run from "./run.js"
5 | import * as exportData from "./export.js"
6 | import * as importData from "./import.js"
7 |
8 | export const commands = [info, run, exportData, importData] as unknown as CommandModule[]
9 |
--------------------------------------------------------------------------------
/packages/cli/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import yargs from "yargs"
4 | import { hideBin } from "yargs/helpers"
5 |
6 | import { commands } from "./commands/index.js"
7 |
8 | export { AppInstance } from "./AppInstance.js"
9 |
10 | commands
11 | .reduce((argv, command) => argv.command(command), yargs(hideBin(process.argv)))
12 | .demandCommand()
13 | .recommendCommands()
14 | .strict()
15 | .scriptName("canvas")
16 | .help()
17 | .parse()
18 |
--------------------------------------------------------------------------------
/packages/cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "dist"
7 | },
8 | "references": [
9 | { "path": "../signer-atp" },
10 | { "path": "../signer-ethereum" },
11 | { "path": "../signer-cosmos" },
12 | { "path": "../signer-solana" },
13 | { "path": "../signer-substrate" },
14 | { "path": "../core" },
15 | { "path": "../gossiplog" },
16 | { "path": "../interfaces" },
17 | { "path": "../network-explorer" }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/packages/client/README.md:
--------------------------------------------------------------------------------
1 | # @canvas-js/client
2 |
3 | The `Client` class here manages one or more session signers to sign and post messages over the Canvas HTTP API.
4 |
5 | The HTTP API in question is very simple:
6 |
7 | - `GET /api/clock` returning `{ clock: number; parents: string[] }`
8 | - `POST /api/insert` carrying `{}`
9 |
--------------------------------------------------------------------------------
/packages/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/client",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "exports": {
16 | ".": "./lib/index.js"
17 | },
18 | "scripts": {
19 | "test": "ava"
20 | },
21 | "peerDependencies": {
22 | "react": "^18.3.1"
23 | },
24 | "dependencies": {
25 | "@canvas-js/interfaces": "0.16.1",
26 | "@canvas-js/modeldb": "0.16.1",
27 | "@canvas-js/utils": "1.0.0",
28 | "@ipld/dag-json": "^10.2.5",
29 | "@noble/hashes": "^1.8.0",
30 | "http-status-codes": "^2.3.0"
31 | },
32 | "devDependencies": {
33 | "@types/react": "^18.3.9",
34 | "react": "^18.3.1"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/client/src/index.ts:
--------------------------------------------------------------------------------
1 | export { Client } from "./Client.js"
2 |
--------------------------------------------------------------------------------
/packages/client/src/useLiveQuery.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo, useState } from "react"
2 |
3 | import * as json from "@ipld/dag-json"
4 |
5 | import type { ModelValue, QueryParams } from "@canvas-js/modeldb"
6 |
7 | import { Client } from "./Client.js"
8 |
9 | export function useLiveQuery(
10 | client: Client | null | undefined,
11 | model: string | null | undefined,
12 | query: Exclude | null | undefined,
13 | ): T[] | null {
14 | const [results, setResults] = useState(null)
15 | const queryString = useMemo(() => json.stringify(query), [query])
16 |
17 | useEffect(() => {
18 | if (!client || !model || !query) {
19 | return
20 | }
21 |
22 | const queryParams = new URLSearchParams({ model, query: queryString })
23 | const eventSource = new EventSource(`${client.host}/api/subscribe?${queryParams}`)
24 | eventSource.addEventListener("message", (event) => {
25 | setResults(json.parse(event.data))
26 | })
27 |
28 | return () => eventSource.close()
29 | }, [client, model, queryString])
30 |
31 | return results
32 | }
33 |
--------------------------------------------------------------------------------
/packages/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../interfaces" }, { "path": "../modeldb" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/internal_docs/images/step_1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/core/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const RUNTIME_MEMORY_LIMIT = 1024 * 640 // 640kb
2 |
3 | export const second = 1000
4 | export const minute = second * 60
5 | export const hour = minute * 60
6 |
7 | export const PING_TIMEOUT = 3 * second
8 |
9 | export const MIN_CONNECTIONS = 5
10 | export const MAX_CONNECTIONS = 300
11 |
12 | export const MIN_MESH_PEERS = 5
13 |
14 | export const DIAL_CONCURRENCY = 10
15 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Canvas.js"
2 | export * from "./types.js"
3 |
4 | export { Contract } from "./contract.js"
5 |
6 | export { TransformActionParams, transactionalize, createClassContract } from "./compatibility.js"
7 | export { TableChange, RowChange, hashContract, generateChangesets } from "./snapshot.js"
8 | export { encodeRecordKey, decodeRecordKey, encodeRecordValue, decodeRecordValue, renderSyncStatus } from "./utils.js"
9 |
10 | export { Action, Session, Snapshot } from "@canvas-js/interfaces"
11 | export { NetworkClient } from "@canvas-js/gossiplog"
12 |
--------------------------------------------------------------------------------
/packages/core/src/runtime/index.ts:
--------------------------------------------------------------------------------
1 | import { SignerCache } from "@canvas-js/interfaces"
2 |
3 | import type { ContractClass } from "../types.js"
4 | import { AbstractRuntime } from "./AbstractRuntime.js"
5 |
6 | import { ClassContractRuntime } from "./ClassContractRuntime.js"
7 | import { ClassFunctionRuntime } from "./ClassFunctionRuntime.js"
8 | import { JSValue } from "@canvas-js/utils"
9 |
10 | export { AbstractRuntime as Runtime } from "./AbstractRuntime.js"
11 |
12 | export async function createRuntime(
13 | contract: string | ContractClass,
14 | args: JSValue[],
15 | signers: SignerCache,
16 | options: { runtimeMemoryLimit?: number } = {},
17 | ): Promise {
18 | if (typeof contract === "string") {
19 | return ClassContractRuntime.init(contract, args, signers, options)
20 | } else {
21 | return ClassFunctionRuntime.init(contract, args, signers)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/targets/default/index.ts:
--------------------------------------------------------------------------------
1 | import type { PlatformTarget } from "../interface.js"
2 |
3 | const target: PlatformTarget = {
4 | async openGossipLog(location, init) {
5 | throw new Error("Unsupported platform")
6 | },
7 |
8 | async listen(app, port, options) {
9 | throw new Error("Unsupported platform")
10 | },
11 |
12 | buildContract(contract: string) {
13 | throw new Error("Unsupported platform")
14 | },
15 |
16 | buildContractByLocation(location: string) {
17 | throw new Error("Unsupported platform")
18 | },
19 | }
20 |
21 | export default target
22 |
--------------------------------------------------------------------------------
/packages/core/src/targets/interface.ts:
--------------------------------------------------------------------------------
1 | import * as pg from "pg"
2 |
3 | import type { MessageType } from "@canvas-js/interfaces"
4 | import type { AbstractGossipLog, GossipLogInit } from "@canvas-js/gossiplog"
5 | import type { Canvas } from "@canvas-js/core"
6 | import type { SqlStorage } from "@cloudflare/workers-types"
7 |
8 | export interface PlatformTarget {
9 | openGossipLog: (
10 | location: { path: string | pg.ConnectionConfig | SqlStorage | null; topic: string },
11 | init: GossipLogInit,
12 | ) => Promise>
13 |
14 | listen: (app: Canvas, port: number, options?: { signal?: AbortSignal }) => Promise
15 |
16 | buildContract: (contract: string, extraConfig?: any) => Promise<{ build: string; originalContract: string }>
17 | buildContractByLocation: (location: string) => Promise<{ build: string; originalContract: string }>
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/src/targets/react-native/index.ts:
--------------------------------------------------------------------------------
1 | import * as pg from "pg"
2 | import type { SqlStorage } from "@cloudflare/workers-types"
3 |
4 | import type { PlatformTarget } from "../interface.js"
5 |
6 | const target: PlatformTarget = {
7 | async openGossipLog(location: { path: string | pg.ConnectionConfig | SqlStorage | null; topic: string }, init) {
8 | if (location.path === null) {
9 | const { GossipLog } = await import("@canvas-js/gossiplog/sqlite-expo")
10 | return await GossipLog.open({ ...init, clear: init.clear })
11 | } else {
12 | throw new Error("Unimplemented: named sqlite dbs on react-native")
13 | }
14 | },
15 |
16 | async listen(app, port, options = {}) {
17 | throw new Error("Unimplemented: libp2p listen on react-native")
18 | },
19 |
20 | buildContract(contract: string) {
21 | throw new Error("Unimplemented: buildContract on react-native")
22 | },
23 |
24 | buildContractByLocation(location: string) {
25 | throw new Error("Unimplemented: buildContractByLocation on react-native")
26 | },
27 | }
28 |
29 | export default target
30 |
--------------------------------------------------------------------------------
/packages/core/test/build.test.ts:
--------------------------------------------------------------------------------
1 | import test from "ava"
2 |
3 | import { Canvas } from "@canvas-js/core"
4 | import fs from "fs"
5 |
6 | test("build a contract in node", async (t) => {
7 | const contract = `const a: number = 1;
8 | console.log(a);
9 | `
10 |
11 | const expectedBuild = `var a = 1;
12 | console.log(a);`
13 |
14 | const { originalContract, build } = await Canvas.buildContract(contract)
15 | t.is(originalContract, contract)
16 | t.is(expectedBuild, build.trimEnd())
17 | })
18 |
19 | test("build a contract by location in node", async (t) => {
20 | const contract = `const a: number = 1;
21 | console.log(a);
22 | `
23 |
24 | const expectedBuild = `var a = 1;
25 | console.log(a);`
26 |
27 | const location = '/tmp/canvas-virtual-contract.ts'
28 | fs.writeFileSync(location, contract)
29 |
30 | const { originalContract, build } = await Canvas.buildContractByLocation(location)
31 | t.is(originalContract, contract)
32 | t.is(expectedBuild, build.trimEnd())
33 | })
--------------------------------------------------------------------------------
/packages/core/test/signers.test.ts:
--------------------------------------------------------------------------------
1 | import test from "ava"
2 |
3 | import { Canvas, ModelSchema } from "@canvas-js/core"
4 | import { Contract } from "@canvas-js/core/contract"
5 | import { PRNGSigner } from "./utils.js"
6 | import { setTimeout } from "timers/promises"
7 |
8 | test("test PRNGSigner", async (t) => {
9 | class MyApp extends Contract {
10 | static topic = "com.example.app"
11 | static models = {} satisfies ModelSchema
12 |
13 | async hello() {}
14 | }
15 |
16 | const app1 = await Canvas.initialize({
17 | contract: MyApp,
18 | signers: [new PRNGSigner(0)],
19 | })
20 |
21 | const app2 = await Canvas.initialize({
22 | contract: MyApp,
23 | signers: [new PRNGSigner(0)],
24 | })
25 |
26 | for (let i = 0; i < 3; i++) {
27 | await app1.actions.hello()
28 | await setTimeout(10)
29 | await app2.actions.hello()
30 | await setTimeout(10)
31 | }
32 |
33 | t.deepEqual(await app1.db.query("$messages"), await app2.db.query("$messages"))
34 | })
35 |
--------------------------------------------------------------------------------
/packages/core/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "module": "Node16",
6 | "moduleResolution": "Node16",
7 | "rootDir": ".",
8 | "outDir": "lib"
9 | },
10 | "references": [
11 | { "path": "../." },
12 | { "path": "../../interfaces" },
13 | { "path": "../../signatures" },
14 | { "path": "../../signer-ethereum" },
15 | { "path": "../../signer-cosmos" }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src", "types"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [
9 | { "path": "../signer-ethereum" },
10 | { "path": "../interfaces" },
11 | { "path": "../signatures" },
12 | { "path": "../modeldb" },
13 | { "path": "../modeldb-idb" },
14 | { "path": "../modeldb-pg" },
15 | { "path": "../modeldb-sqlite" },
16 | { "path": "../gossiplog" },
17 | { "path": "../vm" }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/types/prng-xoshiro.d.ts:
--------------------------------------------------------------------------------
1 | declare class Generator256 {
2 | constructor(s1: bigint, s2: bigint, s3: bigint, s4: bigint)
3 |
4 | /**
5 | * Returns a random BigInt in the range 0 to n (exclusive).
6 | * If n is not provided, it defaults to 0xFFFFFFFFFFFFFFFFn.
7 | */
8 | nextBigInt(n?: bigint): bigint
9 | }
10 |
11 | declare class Generator128 {
12 | constructor(seedLo: number, seedHi: number)
13 |
14 | /**
15 | * Returns a random number in the range 0 to n (exclusive).
16 | * If n is not provided, it defaults to 0xFFFFFFFF.
17 | */
18 | nextNumber(n?: number): number
19 | }
20 |
21 | declare module "prng-xoshiro" {
22 | export class XoShiRo256StarStar extends Generator256 {}
23 | export class XoShiRo256PlusPlus extends Generator256 {}
24 | export class XoShiRo256Plus extends Generator256 {}
25 |
26 | export class XoShiRo128StarStar extends Generator128 {}
27 | export class XoShiRo128PlusPlus extends Generator128 {}
28 | export class XoShiRo128Plus extends Generator128 {}
29 | }
30 |
--------------------------------------------------------------------------------
/packages/ethereum-contracts/.gitignore:
--------------------------------------------------------------------------------
1 | artifacts/
2 | cache/
3 | typechain-types/
4 | .js/
5 |
--------------------------------------------------------------------------------
/packages/ethereum-contracts/hardhat.config.ts:
--------------------------------------------------------------------------------
1 | import { HardhatUserConfig } from "hardhat/config"
2 | import "@nomicfoundation/hardhat-toolbox"
3 | import "@nomicfoundation/hardhat-chai-matchers"
4 |
5 | const config: HardhatUserConfig = {
6 | solidity: "0.8.19",
7 | }
8 |
9 | export default config
10 |
--------------------------------------------------------------------------------
/packages/ethereum-contracts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "target": "es2020",
5 | "esModuleInterop": true,
6 | "noEmitOnError": true,
7 | "noEmit": true,
8 | "allowImportingTsExtensions": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "strict": true,
11 | "skipLibCheck": true,
12 | "outDir": "lib"
13 | },
14 | "references": [{ "path": "../signer-ethereum" }, { "path": "../signatures" }]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/gossiplog/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/bootstrap.ts:
--------------------------------------------------------------------------------
1 | export const defaultBootstrapList = [
2 | "/dns4/canvas-bootstrap-p0.fly.dev/tcp/443/wss/p2p/12D3KooWB3X5KnsTMknFLqxMf2979zddpwCWZ5ix1FosV5sFC4py",
3 | "/dns4/canvas-bootstrap-p1.fly.dev/tcp/443/wss/p2p/12D3KooWCSsK4VGWx4gA2FiUKQFEHyLrL6bf4xAJm6kqB2gkB59w",
4 | "/dns4/canvas-bootstrap-p2.fly.dev/tcp/443/wss/p2p/12D3KooWHWLKtWifVTMwJQRG6jcVCV3kLQ49GCB4DgevLTnKWor3",
5 | ]
6 |
7 | export const testnetBootstrapList = [
8 | "/dns4/canvas-testnet-p0.fly.dev/tcp/443/wss/p2p/12D3KooWQoSrJD9g9D1qeLWwUE9JomQeEMDeEmtRRG2znudA9bm8",
9 | "/dns4/canvas-testnet-p1.fly.dev/tcp/443/wss/p2p/12D3KooWPWiKt9yrdDBhL9dQ25j4F2VZS12sgGeQYhRn66WBiCmX",
10 | "/dns4/canvas-testnet-p2.fly.dev/tcp/443/wss/p2p/12D3KooWFGBSKFVxM5XUvTFcniDpJHUtggY7meTzz9MpoVN72eQy",
11 | ]
12 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const second = 1000
2 | export const minute = second * 60
3 | export const hour = minute * 60
4 |
5 | export const MAX_INBOUND_STREAMS = 64
6 | export const MAX_OUTBOUND_STREAMS = 64
7 |
8 | export const SYNC_RETRY_INTERVAL = 3 * second // this is multiplied by Math.random()
9 | export const SYNC_RETRY_LIMIT = 5
10 |
11 | export const SYNC_TIMEOUT = 20 * second
12 |
13 | export const DEFAULT_PROTOCOL_SELECT_TIMEOUT = 1000
14 | export const NEGOTIATE_FULLY = true
15 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/errors.ts:
--------------------------------------------------------------------------------
1 | import type { Node } from "@canvas-js/okra"
2 |
3 | export class MissingParentError extends Error {
4 | public static code = "MISSING_PARENT"
5 | public readonly code = MissingParentError.name
6 |
7 | constructor(public readonly parent: string, public readonly id: string) {
8 | super(`missing parent ${parent} of ${id}`)
9 | }
10 | }
11 |
12 | export class MessageNotFoundError extends Error {
13 | public static code = "MESSAGE_NOT_FOUND"
14 | public readonly code = MessageNotFoundError.name
15 |
16 | constructor(public readonly id: string) {
17 | super(`message ${id} not found`)
18 | }
19 | }
20 |
21 | export class ConflictError extends Error {
22 | public static code = "CONFLICT"
23 | public readonly code = ConflictError.name
24 |
25 | public constructor(public readonly source: Node, public readonly target: Node) {
26 | super(`conflicting values for key`)
27 | }
28 | }
29 |
30 | export class AbortError extends Error {
31 | public static code = "ABORT"
32 | public readonly code = AbortError.name
33 |
34 | constructor() {
35 | super("sync aborted by server")
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/index.ts:
--------------------------------------------------------------------------------
1 | export type { Message } from "@canvas-js/interfaces"
2 |
3 | export * from "./AbstractGossipLog.js"
4 | export * from "./clock.js"
5 | export * from "./interface.js"
6 | export * from "./MessageId.js"
7 | export * from "./MessageSet.js"
8 | export * from "./SignedMessage.js"
9 | export type { AncestorRecord } from "./AncestorIndex.js"
10 |
11 | export { topicPattern as gossiplogTopicPattern } from "./utils.js"
12 | export { NetworkClient } from "./client/index.js"
13 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/interface.ts:
--------------------------------------------------------------------------------
1 | import type { Awaitable } from "@canvas-js/utils"
2 | import type { SyncSource } from "@canvas-js/okra"
3 |
4 | export interface SyncSnapshot extends SyncSource {
5 | getValues(keys: Uint8Array[]): Awaitable
6 | }
7 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/libp2p/index.ts:
--------------------------------------------------------------------------------
1 | export { getLibp2p } from "#target/libp2p"
2 |
3 | export * from "./interface.js"
4 | export * from "./service.js"
5 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/protocols/events.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | message Event {
4 | message Insert {
5 | bytes key = 1;
6 | bytes value = 2;
7 | }
8 |
9 | message Update {
10 | repeated bytes heads = 1;
11 | }
12 |
13 | oneof event {
14 | Insert insert = 1;
15 | Update update = 2;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/sync/index.ts:
--------------------------------------------------------------------------------
1 | export { Client, decodeResponses, encodeRequests } from "./client.js"
2 | export { Server, encodeResponses, decodeRequests } from "./server.js"
3 | export { Driver } from "./driver.js"
4 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/sync/utils.ts:
--------------------------------------------------------------------------------
1 | import type { Key, Node } from "@canvas-js/okra"
2 |
3 | import * as Sync from "@canvas-js/gossiplog/protocols/sync"
4 |
5 | export const encodeKey = (key: Key) => key ?? undefined
6 |
7 | export function encodeNode({ level, key, hash }: Node): Sync.Node {
8 | return { level, key: encodeKey(key), hash }
9 | }
10 |
11 | export function decodeKey(key: Uint8Array | undefined) {
12 | if (key === undefined || key.length === 0) {
13 | return null
14 | } else {
15 | return key
16 | }
17 | }
18 |
19 | export function decodeNode({ level, key, hash }: Sync.Node): Node {
20 | return { level, key: decodeKey(key), hash }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/targets/browser/privateKey.ts:
--------------------------------------------------------------------------------
1 | import { PrivateKey } from "@libp2p/interface"
2 | import { generateKeyPair, privateKeyFromProtobuf } from "@libp2p/crypto/keys"
3 |
4 | export async function getPrivateKey(): Promise {
5 | // TODO: persist keys to localstorage
6 | return await generateKeyPair("Ed25519")
7 |
8 | // const { LIBP2P_PRIVATE_KEY } = process.env
9 | // if (typeof LIBP2P_PRIVATE_KEY === "string") {
10 | // return privateKeyFromProtobuf(Buffer.from(LIBP2P_PRIVATE_KEY, "base64"))
11 | // } else {
12 | // }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/targets/default/index.ts:
--------------------------------------------------------------------------------
1 | import type { PlatformTarget } from "../interface.js"
2 |
3 | const target: PlatformTarget = {
4 | async connect(gossipLog, url, options) {
5 | throw new Error("Unsupported platform")
6 | },
7 |
8 | async listen(gossipLog, port, options) {
9 | throw new Error("Unsupported platform")
10 | },
11 |
12 | async startLibp2p(gossipLog, config) {
13 | throw new Error("Unsupported platform")
14 | },
15 | }
16 |
17 | export default target
18 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/targets/default/libp2p.ts:
--------------------------------------------------------------------------------
1 | import { Libp2p } from "@libp2p/interface"
2 |
3 | import { AbstractGossipLog } from "@canvas-js/gossiplog"
4 | import { NetworkConfig, ServiceMap } from "@canvas-js/gossiplog/libp2p"
5 |
6 | export async function getLibp2p(
7 | gossipLog: AbstractGossipLog,
8 | config: NetworkConfig,
9 | ): Promise>> {
10 | throw new Error("Unsupported platform")
11 | }
12 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/targets/interface.ts:
--------------------------------------------------------------------------------
1 | import type { Libp2p } from "@libp2p/interface"
2 |
3 | import type { AbstractGossipLog } from "../AbstractGossipLog.js"
4 | import type { NetworkClient } from "@canvas-js/gossiplog/client"
5 |
6 | import type { NetworkConfig, ServiceMap } from "@canvas-js/gossiplog/libp2p"
7 |
8 | export type AbortOptions = { signal?: AbortSignal }
9 |
10 | export interface PlatformTarget {
11 | connect: (
12 | gossipLog: AbstractGossipLog,
13 | url: string,
14 | options?: AbortOptions,
15 | ) => Promise>
16 | listen: (gossipLog: AbstractGossipLog, port: number, options?: AbortOptions) => Promise
17 | startLibp2p: (
18 | gossipLog: AbstractGossipLog,
19 | config: NetworkConfig,
20 | ) => Promise>>
21 | }
22 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/targets/node/privateKey.ts:
--------------------------------------------------------------------------------
1 | import process from "node:process"
2 |
3 | import { PrivateKey } from "@libp2p/interface"
4 | import { generateKeyPair, privateKeyFromProtobuf } from "@libp2p/crypto/keys"
5 |
6 | export async function getPrivateKey(): Promise {
7 | const { LIBP2P_PRIVATE_KEY } = process.env
8 | if (typeof LIBP2P_PRIVATE_KEY === "string") {
9 | return privateKeyFromProtobuf(Buffer.from(LIBP2P_PRIVATE_KEY, "base64"))
10 | } else {
11 | return await generateKeyPair("Ed25519")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/targets/react-native/index.ts:
--------------------------------------------------------------------------------
1 | import { anySignal } from "any-signal"
2 |
3 | import { NetworkClient } from "@canvas-js/gossiplog/client"
4 | import { assert } from "@canvas-js/utils"
5 |
6 | import type { PlatformTarget } from "../interface.js"
7 |
8 | const target: PlatformTarget = {
9 | async connect(gossipLog, url, options = {}) {
10 | assert(url.startsWith("ws://") || url.startsWith("wss://"), "url must start with ws:// or wss://")
11 |
12 | const client = new NetworkClient(gossipLog, url)
13 | const signal = anySignal([gossipLog.controller.signal, options.signal])
14 | signal.addEventListener("abort", () => client.close())
15 | await client.duplex.connected()
16 |
17 | return client
18 | },
19 |
20 | async listen(gossipLog, port, options) {
21 | throw new Error("Cannot start API server in react native")
22 | },
23 |
24 | async startLibp2p(gossipLog, config) {
25 | throw new Error("Cannot start libp2p in react native")
26 | },
27 | }
28 |
29 | export default target
30 |
--------------------------------------------------------------------------------
/packages/gossiplog/src/targets/react-native/libp2p.ts:
--------------------------------------------------------------------------------
1 | import { Libp2p } from "@libp2p/interface"
2 |
3 | import { AbstractGossipLog } from "@canvas-js/gossiplog"
4 | import { NetworkConfig, ServiceMap } from "@canvas-js/gossiplog/libp2p"
5 |
6 | export async function getLibp2p(
7 | gossipLog: AbstractGossipLog,
8 | config: NetworkConfig,
9 | ): Promise>> {
10 | throw new Error("Unsupported platform")
11 | }
12 |
--------------------------------------------------------------------------------
/packages/gossiplog/test/result.test.ts:
--------------------------------------------------------------------------------
1 | import { randomUUID } from "node:crypto"
2 | import { sha256 } from "@noble/hashes/sha256"
3 | import { bytesToHex } from "@noble/hashes/utils"
4 |
5 | import { ed25519 } from "@canvas-js/signatures"
6 |
7 | import type { GossipLogConsumer } from "@canvas-js/gossiplog"
8 | import { testPlatforms } from "./utils.js"
9 |
10 | const apply: GossipLogConsumer = ({ message: { payload } }) => bytesToHex(sha256(payload))
11 |
12 | testPlatforms(
13 | "get apply result",
14 | async (t, openGossipLog) => {
15 | const topic = randomUUID()
16 | const log = await openGossipLog(t, { topic, apply })
17 |
18 | const signer = ed25519.create()
19 | const { result: resultA } = await log.append("foo", { signer })
20 | const { result: resultB } = await log.append("bar", { signer })
21 | const { result: resultC } = await log.append("baz", { signer })
22 |
23 | t.is(resultA, bytesToHex(sha256("foo")))
24 | t.is(resultB, bytesToHex(sha256("bar")))
25 | t.is(resultC, bytesToHex(sha256("baz")))
26 | },
27 | { memory: true },
28 | )
29 |
--------------------------------------------------------------------------------
/packages/gossiplog/test/sync.test.ts:
--------------------------------------------------------------------------------
1 | import { setTimeout } from "node:timers/promises"
2 | import { randomUUID } from "node:crypto"
3 |
4 | import { nanoid } from "nanoid"
5 |
6 | import type { GossipLogConsumer } from "@canvas-js/gossiplog"
7 |
8 | import { testPlatforms } from "./utils.js"
9 |
10 | const apply: GossipLogConsumer = ({}) => {}
11 |
12 | testPlatforms(
13 | "ws publish",
14 | async (t, openGossipLog) => {
15 | const topic = randomUUID()
16 | const a = await openGossipLog(t, { topic, apply })
17 | const b = await openGossipLog(t, { topic, apply })
18 | const c = await openGossipLog(t, { topic, apply })
19 |
20 | await a.listen(5555)
21 | await b.connect("ws://127.0.0.1:5555")
22 | await c.connect("ws://127.0.0.1:5555")
23 |
24 | await setTimeout(1000)
25 |
26 | const { id, signature, message } = await b.append(nanoid())
27 |
28 | await setTimeout(1000)
29 |
30 | const result = await c.get(id)
31 | t.deepEqual(result?.signature, signature)
32 | t.deepEqual(result?.message, message)
33 |
34 | t.pass()
35 | },
36 | { sqlite: true },
37 | )
38 |
--------------------------------------------------------------------------------
/packages/gossiplog/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "module": "Node16",
6 | "moduleResolution": "Node16",
7 | "rootDir": ".",
8 | "outDir": "lib"
9 | },
10 | "references": [{ "path": "../." }, { "path": "../../signatures" }, { "path": "../../interfaces" }]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/gossiplog/test/web-locks.d.ts:
--------------------------------------------------------------------------------
1 | declare module "web-locks" {
2 | export const locks: LockManager
3 | export class AbortController extends AbortController {}
4 | }
5 |
--------------------------------------------------------------------------------
/packages/gossiplog/test/worker.ts:
--------------------------------------------------------------------------------
1 | import { ExecutionContext, DurableObjectNamespace, Request } from "@cloudflare/workers-types"
2 |
3 | // export and bind durable objects defined in wrangler.toml
4 | export { ModelDBProxyObject } from "@canvas-js/modeldb-durable-objects"
5 |
6 | export interface Env {
7 | PROXY_OBJECT: DurableObjectNamespace
8 | }
9 |
10 | export default {
11 | async fetch(request: Request, env: Env, ctx: ExecutionContext) {
12 | const url = new URL(request.url)
13 | const uuid = url.pathname.split("/")[1]
14 | const id = env.PROXY_OBJECT.idFromName(uuid)
15 | const proxyObject = env.PROXY_OBJECT.get(id)
16 |
17 | // forward request to durable object
18 | return proxyObject.fetch(request)
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/packages/gossiplog/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [
9 | { "path": "../interfaces" },
10 | { "path": "../modeldb" },
11 | { "path": "../modeldb-idb" },
12 | { "path": "../modeldb-pg" },
13 | { "path": "../modeldb-sqlite" },
14 | { "path": "../modeldb-sqlite-expo" },
15 | { "path": "../modeldb-durable-objects" },
16 | { "path": "../signatures" }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/gossiplog/wrangler.toml:
--------------------------------------------------------------------------------
1 | name = "modeldb"
2 | compatibility_date = "2024-09-25"
3 | compatibility_flags = ["nodejs_compat_v2"]
4 |
5 | [durable_objects]
6 | bindings = [
7 | { name = "PROXY_OBJECT", class_name = "ModelDBProxyObject" }
8 | ]
9 |
10 | [[migrations]]
11 | tag = "v1" # Should be unique for each entry
12 | new_sqlite_classes = ["ModelDBProxyObject"]
13 |
--------------------------------------------------------------------------------
/packages/hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/hooks",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
6 | "main": "lib/index.js",
7 | "types": "lib/index.d.ts",
8 | "sideEffects": false,
9 | "files": [
10 | "lib"
11 | ],
12 | "exports": {
13 | ".": "./lib/index.js",
14 | "./components": "./lib/components.js",
15 | "./auth": "./lib/auth/index.js",
16 | "./provider": "./lib/provider/index.js"
17 | },
18 | "peerDependencies": {
19 | "react": "^18.3.1"
20 | },
21 | "dependencies": {
22 | "@canvas-js/core": "0.16.1",
23 | "@canvas-js/gossiplog": "0.16.1",
24 | "@canvas-js/interfaces": "0.16.1",
25 | "@canvas-js/modeldb": "0.16.1",
26 | "@canvas-js/modeldb-idb": "0.16.1",
27 | "@farcaster/auth-kit": "^0.8.1",
28 | "@farcaster/frame-core": "^0.1.8",
29 | "@farcaster/frame-sdk": "^0.0.61",
30 | "@ipld/dag-cbor": "^9.2.4",
31 | "@noble/hashes": "^1.8.0"
32 | },
33 | "devDependencies": {
34 | "@types/react": "^18.3.9",
35 | "react": "^18.3.1"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/hooks/src/auth/index.ts:
--------------------------------------------------------------------------------
1 | export { AuthContext, AuthProvider } from "./AuthContext.js"
2 | export { useSIWE, ConnectSIWEProps } from "./useSIWE.js"
3 | export { useSIWF, ConnectSIWFProps } from "./useSIWF.js"
4 | export { useLogout, LogoutProps } from "./useLogout.js"
5 | export { styles } from "./styles.js"
6 |
--------------------------------------------------------------------------------
/packages/hooks/src/auth/styles.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from 'react'
2 |
3 | export const button = {
4 | padding: "0.5rem",
5 | border: "1px solid",
6 | borderRadius: "0.25rem",
7 | } satisfies CSSProperties
8 |
9 | export const styles = {
10 | errorContainer: {
11 | padding: "0.5rem",
12 | border: "1px solid",
13 | borderRadius: "0.25rem",
14 | backgroundColor: "#fee2e2",
15 | fontSize: "0.875rem",
16 | } satisfies CSSProperties,
17 |
18 | loadingButton: {
19 | ...button,
20 | backgroundColor: "#transparent",
21 | width: "100%",
22 | } satisfies CSSProperties,
23 |
24 | actionButton: {
25 | ...button,
26 | cursor: "pointer",
27 | width: "100%",
28 | } satisfies CSSProperties,
29 | }
--------------------------------------------------------------------------------
/packages/hooks/src/components.ts:
--------------------------------------------------------------------------------
1 | export { useTick } from "./useTick.js"
2 | export { useCanvas } from "./useCanvas.js"
3 | export { useLiveQuery } from "./useLiveQuery.js"
4 | export { useSyncStatus } from "./useSyncStatus.js"
5 | export { useClock } from "./useClock.js"
6 |
7 | export { AppInfo } from "./AppInfo.js"
8 |
--------------------------------------------------------------------------------
/packages/hooks/src/index.ts:
--------------------------------------------------------------------------------
1 | // components.js
2 | export { useTick } from "./useTick.js"
3 | export { useCanvas } from "./useCanvas.js"
4 | export { useLiveQuery } from "./useLiveQuery.js"
5 | export { useSyncStatus } from "./useSyncStatus.js"
6 | export { useClock } from "./useClock.js"
7 |
8 | export { AppInfo } from "./AppInfo.js"
9 |
10 | // auth/index.js
11 | export { AuthContext, AuthProvider } from "./auth/AuthContext.js"
12 | export { useSIWE, ConnectSIWEProps } from "./auth/useSIWE.js"
13 | export { useSIWF, ConnectSIWFProps } from "./auth/useSIWF.js"
14 | export { useLogout, LogoutProps } from "./auth/useLogout.js"
15 | export { styles } from "./auth/styles.js"
16 |
--------------------------------------------------------------------------------
/packages/hooks/src/provider/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./hooks.js"
2 | export * from "./provider.js"
--------------------------------------------------------------------------------
/packages/hooks/src/useClock.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react"
2 | import { Canvas } from "@canvas-js/core"
3 |
4 | export const useClock = (app: Canvas | null | undefined) => {
5 | const [clock, setClock] = useState(0)
6 | useEffect(() => {
7 | if (!app) return
8 | const updateClock = () => {
9 | app.messageLog.getClock().then(([nextClock, heads]: [number, string[]]) => {
10 | if (nextClock - 1 > clock) setClock(nextClock - 1)
11 | })
12 | }
13 | updateClock()
14 | app.messageLog.addEventListener("message", updateClock)
15 | app.messageLog.addEventListener("sync", updateClock)
16 | app.messageLog.addEventListener("sync:status", updateClock)
17 | return () => {
18 | app.messageLog.removeEventListener("message", updateClock)
19 | app.messageLog.removeEventListener("sync", updateClock)
20 | app.messageLog.removeEventListener("sync:status", updateClock)
21 | }
22 | }, [app])
23 | return clock
24 | }
25 |
--------------------------------------------------------------------------------
/packages/hooks/src/useLiveQuery.ts:
--------------------------------------------------------------------------------
1 | import type { Canvas, ModelSchema } from "@canvas-js/core"
2 | import type { QueryParams } from "@canvas-js/modeldb"
3 | import { useLiveQuery as _useLiveQuery } from "@canvas-js/modeldb-idb"
4 |
5 | export function useLiveQuery<
6 | ModelsT extends ModelSchema,
7 | K extends keyof ModelsT & string,
8 | Q extends QueryParams = QueryParams,
9 | >(app: Canvas | null | undefined, modelName: K, query?: Q | null) {
10 | return _useLiveQuery(app?.db, modelName, query as Q | null | undefined)
11 | }
12 |
--------------------------------------------------------------------------------
/packages/hooks/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib",
7 | "jsx": "react"
8 | },
9 | "references": [
10 | { "path": "../core" },
11 | { "path": "../interfaces" },
12 | { "path": "../modeldb" },
13 | { "path": "../modeldb-idb" }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/interfaces/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/interfaces/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/interfaces",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
6 | "main": "lib/index.js",
7 | "types": "lib/index.d.ts",
8 | "sideEffects": false,
9 | "files": [
10 | "lib"
11 | ],
12 | "scripts": {
13 | "test": "ava"
14 | },
15 | "devDependencies": {
16 | "@cosmjs/amino": "^0.32.3",
17 | "@noble/curves": "^1.9.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/interfaces/src/Action.ts:
--------------------------------------------------------------------------------
1 | export type Action = {
2 | type: "action"
3 |
4 | /** DID of the user that authorized the session (e.g. "did:pkh:eip155:1:0xb94d27...") */
5 | did: `did:${string}`
6 |
7 | name: string
8 | args: any
9 |
10 | context: {
11 | timestamp: number
12 | blockhash?: string
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/interfaces/src/Awaitable.ts:
--------------------------------------------------------------------------------
1 | export type Awaitable = T | Promise
2 |
--------------------------------------------------------------------------------
/packages/interfaces/src/Message.ts:
--------------------------------------------------------------------------------
1 | export type Message = {
2 | topic: string
3 | clock: number
4 | parents: string[]
5 | payload: Payload
6 | }
7 |
--------------------------------------------------------------------------------
/packages/interfaces/src/MessageType.ts:
--------------------------------------------------------------------------------
1 | import { Action } from "./Action.js"
2 | import { Session } from "./Session.js"
3 | import { Snapshot } from "./Snapshot.js"
4 |
5 | export type MessageType = Action | Session | Snapshot
6 |
--------------------------------------------------------------------------------
/packages/interfaces/src/Session.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Sessions consist of an ephemeral keypair and some session signer-specific
3 | * data representing a user's (temporary) authorization of that
4 | * keypair to sign actions on their behalf.
5 | */
6 | export type Session = {
7 | type: "session"
8 |
9 | /** DID of the user that authorized the session (e.g. "did:pkh:eip155:1:0xb94d27...") */
10 | did: `did:${string}`
11 |
12 | /** did:key URI of the ephemeral session key used to sign subsequent actions */
13 | publicKey: string
14 |
15 | /** signer-specific session payload, e.g. a SIWE message & signature */
16 | authorizationData: AuthorizationData
17 |
18 | context: {
19 | timestamp: number
20 | blockhash?: string
21 | duration?: number
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/interfaces/src/Signature.ts:
--------------------------------------------------------------------------------
1 | export type Signature = {
2 | codec: string // "dag-cbor" | "dag-json" | "canvas-action-eip712" | "canvas-session-eip712" | "canvas-snapshot"
3 | publicKey: string // did:key URI, or empty string if snapshot
4 | signature: Uint8Array
5 | }
6 |
--------------------------------------------------------------------------------
/packages/interfaces/src/Signer.ts:
--------------------------------------------------------------------------------
1 | import type { Awaitable } from "./Awaitable.js"
2 | import type { Message } from "./Message.js"
3 | import type { Signature } from "./Signature.js"
4 |
5 | export interface SignatureScheme {
6 | type: string
7 | codecs: string[]
8 | verify: (signature: Signature, message: Message) => Awaitable
9 | create: (init?: { type: string; privateKey: Uint8Array }) => Signer
10 | }
11 |
12 | export interface Signer {
13 | scheme: SignatureScheme
14 |
15 | publicKey: string // did:key URI
16 |
17 | sign(message: Message, options?: { codec?: string }): Awaitable
18 |
19 | export(): { type: string; privateKey: Uint8Array }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/interfaces/src/SignerCache.ts:
--------------------------------------------------------------------------------
1 | import type { SessionSigner } from "./SessionSigner.js"
2 |
3 | export class SignerCache {
4 | signers: SessionSigner[]
5 | private default: string | undefined // this is exposed just for debugging
6 |
7 | constructor(signers: SessionSigner[] = []) {
8 | this.signers = signers
9 | if (signers[0]) {
10 | this.default = signers[0].key
11 | }
12 | }
13 |
14 | updateSigners(signers: SessionSigner[]) {
15 | this.signers = signers
16 | if (signers[0]) {
17 | this.default = signers[0].key
18 | }
19 | }
20 |
21 | getAll() {
22 | return this.signers
23 | }
24 |
25 | getFirst() {
26 | return this.signers[0]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/interfaces/src/Snapshot.ts:
--------------------------------------------------------------------------------
1 | export type Snapshot = {
2 | type: "snapshot"
3 | models: Record
4 | }
5 |
--------------------------------------------------------------------------------
/packages/interfaces/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Signature.js"
2 | export * from "./Signer.js"
3 | export * from "./Action.js"
4 | export * from "./SessionSigner.js"
5 | export * from "./SignerCache.js"
6 | export * from "./Message.js"
7 | export * from "./Session.js"
8 | export * from "./Snapshot.js"
9 | export * from "./Awaitable.js"
10 | export * from "./MessageType.js"
11 |
--------------------------------------------------------------------------------
/packages/interfaces/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib"
7 | },
8 | "references": [
9 | { "path": "../." },
10 | { "path": "../../interfaces" },
11 | { "path": "../../signatures" },
12 | { "path": "../../signer-cosmos" },
13 | { "path": "../../signer-ethereum" },
14 | { "path": "../../signer-ethereum-viem" },
15 | { "path": "../../signer-solana" },
16 | { "path": "../../signer-substrate" }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/interfaces/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/modeldb-durable-objects/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/modeldb-durable-objects",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "dependencies": {
16 | "@canvas-js/modeldb": "0.16.1",
17 | "@canvas-js/utils": "1.0.0",
18 | "@cloudflare/workers-types": "^4.20250610.0",
19 | "@ipld/dag-json": "^10.2.5",
20 | "microcbor": "^1.2.0",
21 | "wrangler": "^4.19.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/modeldb-durable-objects/src/ModelDBProxyWorker.ts:
--------------------------------------------------------------------------------
1 | import { ExecutionContext, DurableObjectNamespace, Request } from "@cloudflare/workers-types"
2 |
3 | // export and bind durable objects defined in wrangler.toml
4 | export { ModelDBProxyObject } from "./ModelDBProxyObject.js"
5 | export interface Env {
6 | PROXY_OBJECT: DurableObjectNamespace
7 | }
8 |
9 | export default {
10 | async fetch(request: Request, env: Env, ctx: ExecutionContext) {
11 | const url = new URL(request.url)
12 | const uuid = url.pathname.split("/")[1]
13 | const id = env.PROXY_OBJECT.idFromName(uuid)
14 | const proxyObject = env.PROXY_OBJECT.get(id)
15 |
16 | // forward request to durable object
17 | return proxyObject.fetch(request)
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/packages/modeldb-durable-objects/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ModelDB } from "./ModelDB.js"
2 | export { ModelDBProxy } from "./ModelDBProxy.js"
3 | export { ModelDBProxyObject } from "./ModelDBProxyObject.js"
4 |
--------------------------------------------------------------------------------
/packages/modeldb-durable-objects/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib",
7 | "types": ["@cloudflare/workers-types"]
8 | },
9 | "references": [{ "path": "../modeldb" }]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/modeldb-durable-objects/wrangler.toml:
--------------------------------------------------------------------------------
1 | name = "modeldb-durable-objects"
2 | compatibility_date = "2024-09-25"
3 | compatibility_flags = ["nodejs_compat_v2"]
4 |
5 | [durable_objects]
6 | bindings = [
7 | { name = "PROXY_OBJECT", class_name = "ModelDBProxyObject" }
8 | ]
9 |
10 | [[migrations]]
11 | tag = "v1" # Should be unique for each entry
12 | new_sqlite_classes = ["ModelDBProxyObject"]
13 |
--------------------------------------------------------------------------------
/packages/modeldb-idb/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/modeldb-idb",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "scripts": {
16 | "test": "ava --serial"
17 | },
18 | "peerDependencies": {
19 | "@types/react": "^18.3.9",
20 | "react": "^18.3.1"
21 | },
22 | "dependencies": {
23 | "@canvas-js/modeldb": "0.16.1",
24 | "@canvas-js/utils": "1.0.0",
25 | "@ipld/dag-json": "^10.2.5",
26 | "@libp2p/logger": "^5.1.13",
27 | "idb": "^8.0.3"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/modeldb-idb/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ModelDB } from "./ModelDB.js"
2 | export { useLiveQuery } from "./useLiveQuery.js"
3 |
--------------------------------------------------------------------------------
/packages/modeldb-idb/src/utils.ts:
--------------------------------------------------------------------------------
1 | export const getIndexName = (index: string[]) => index.join("/")
2 |
--------------------------------------------------------------------------------
/packages/modeldb-idb/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../modeldb" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/modeldb-pg/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/modeldb-pg",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "scripts": {
16 | "test": "ava --serial"
17 | },
18 | "dependencies": {
19 | "@canvas-js/modeldb": "0.16.1",
20 | "@canvas-js/utils": "1.0.0",
21 | "@ipld/dag-json": "^10.2.5",
22 | "@types/pg": "^8.11.11",
23 | "@types/pg-cursor": "^2.7.2",
24 | "pg": "^8.15.6",
25 | "pg-cursor": "^2.15.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/modeldb-pg/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ModelDB } from "./ModelDB.js"
2 |
--------------------------------------------------------------------------------
/packages/modeldb-pg/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../modeldb" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite-expo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/modeldb-sqlite-expo",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "scripts": {
16 | "test": "ava --serial"
17 | },
18 | "dependencies": {
19 | "@canvas-js/modeldb": "0.16.1",
20 | "@canvas-js/utils": "1.0.0",
21 | "@ipld/dag-json": "^10.2.5",
22 | "expo-sqlite": "^14.0.6"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite-expo/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ModelDB } from "./ModelDB.js"
2 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite-expo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../modeldb" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite-wasm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/modeldb-sqlite-wasm",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "dependencies": {
16 | "@canvas-js/interfaces": "0.16.1",
17 | "@canvas-js/modeldb": "0.16.1",
18 | "@canvas-js/utils": "1.0.0",
19 | "@ipld/dag-json": "^10.2.5",
20 | "@libp2p/logger": "^5.1.13",
21 | "@sqlite.org/sqlite-wasm": "^3.50.1-build1",
22 | "comlink": "^4.4.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite-wasm/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ModelDB } from "./ModelDB.js"
2 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite-wasm/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "lib": ["WebWorker"],
6 | "rootDir": "src",
7 | "outDir": "lib"
8 | },
9 | "references": [{ "path": "../interfaces" }, { "path": "../modeldb" }]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/modeldb-sqlite",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "dependencies": {
16 | "@canvas-js/modeldb": "0.16.1",
17 | "@canvas-js/utils": "1.0.0",
18 | "@ipld/dag-json": "^10.2.5",
19 | "@types/better-sqlite3": "^7.6.10",
20 | "better-sqlite3": "^11.8.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ModelDB } from "./ModelDB.js"
2 |
--------------------------------------------------------------------------------
/packages/modeldb-sqlite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../modeldb" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/modeldb/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | watchMode: {
3 | ignoreChanges: ["test/server/vite.config.js*", ".wrangler/**/*"],
4 | },
5 | files: ["./test/*.test.ts"],
6 | concurrency: 1,
7 | typescript: {
8 | compile: false,
9 | rewritePaths: {
10 | "test/": "test/lib/",
11 | },
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/modeldb/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/modeldb",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "scripts": {
16 | "test": "ava --serial --timeout 20s"
17 | },
18 | "devDependencies": {
19 | "@canvas-js/modeldb-durable-objects": "0.16.1",
20 | "@canvas-js/modeldb-sqlite-expo": "0.16.1",
21 | "@canvas-js/modeldb-sqlite-wasm": "0.16.1",
22 | "@cloudflare/workers-types": "^4.20250610.0",
23 | "@sqlite.org/sqlite-wasm": "^3.50.1-build1",
24 | "fake-indexeddb": "^6.0.1",
25 | "nanoid": "^5.0.9",
26 | "wrangler": "^4.19.1"
27 | },
28 | "dependencies": {
29 | "@canvas-js/utils": "1.0.0",
30 | "@ipld/dag-json": "^10.2.5",
31 | "@libp2p/logger": "^5.1.13",
32 | "uint8arrays": "^5.1.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/modeldb/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./types.js"
2 | export * from "./config.js"
3 | export * from "./query.js"
4 | export * from "./AbstractModelDB.js"
5 |
6 | export {
7 | validateModelValue,
8 | validatePropertyValue,
9 | mergeModelValue,
10 | updateModelValue,
11 | isPrimitiveValue,
12 | getModelsFromInclude,
13 | isPrimaryKey,
14 | isReferenceValue,
15 | isRelationValue,
16 | equalPrimaryKeys,
17 | equalReferences,
18 | } from "./utils.js"
19 |
--------------------------------------------------------------------------------
/packages/modeldb/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib",
7 | "types": ["@cloudflare/workers-types"]
8 | },
9 | "references": [
10 | { "path": "../." },
11 | { "path": "../../modeldb-idb" },
12 | { "path": "../../modeldb-pg" },
13 | { "path": "../../modeldb-sqlite" },
14 | { "path": "../../modeldb-sqlite-wasm" },
15 | { "path": "../../modeldb-durable-objects" }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/modeldb/test/worker-durable-objects.ts:
--------------------------------------------------------------------------------
1 | import { ExecutionContext, DurableObjectNamespace, Request } from "@cloudflare/workers-types"
2 |
3 | // export and bind durable objects defined in wrangler.toml
4 | export { ModelDBProxyObject } from "@canvas-js/modeldb-durable-objects"
5 |
6 | export interface Env {
7 | PROXY_OBJECT: DurableObjectNamespace
8 | }
9 |
10 | export default {
11 | async fetch(request: Request, env: Env, ctx: ExecutionContext) {
12 | const url = new URL(request.url)
13 | const uuid = url.pathname.split("/")[1]
14 | const id = env.PROXY_OBJECT.idFromName(uuid)
15 | const proxyObject = env.PROXY_OBJECT.get(id)
16 |
17 | // forward request to durable object
18 | return proxyObject.fetch(request)
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/packages/modeldb/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/modeldb/wrangler.toml:
--------------------------------------------------------------------------------
1 | name = "modeldb"
2 | compatibility_date = "2024-09-25"
3 | compatibility_flags = ["nodejs_compat_v2"]
4 |
5 | [durable_objects]
6 | bindings = [
7 | { name = "PROXY_OBJECT", class_name = "ModelDBProxyObject" }
8 | ]
9 |
10 | [[migrations]]
11 | tag = "v1" # Should be unique for each entry
12 | new_sqlite_classes = ["ModelDBProxyObject"]
13 |
--------------------------------------------------------------------------------
/packages/network-explorer/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/packages/network-explorer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Application Explorer
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/network-explorer/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/AdminView.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Heading } from "@radix-ui/themes"
2 | // import { useContractData } from "./hooks/useContractData.js"
3 |
4 | export const AdminView = () => {
5 | return (
6 |
7 |
8 | Admin View
9 |
10 |
11 | TODO
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/columndefMeta.d.ts:
--------------------------------------------------------------------------------
1 | import "@tanstack/react-table" //or vue, svelte, solid, qwik, etc.
2 |
3 | declare module "@tanstack/react-table" {
4 | interface ColumnMeta {
5 | textFilter?: boolean
6 | filterOptions?: string[]
7 | editCell?: ColumnDefTemplate>
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@radix-ui/themes"
2 | import { Link as ReactRouterLink } from "react-router-dom"
3 | import { useTheme } from "../hooks/useTheme.js"
4 |
5 | function Navbar() {
6 | const { theme } = useTheme()
7 |
8 | return (
9 |
10 |
18 | 🌐 Explorer
19 |
20 |
21 | )
22 | }
23 |
24 | export default Navbar
25 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/components/PaginationButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@radix-ui/themes"
2 |
3 | function PaginationButton({ text, onClick, enabled }: { text: string; enabled: boolean; onClick: () => void }) {
4 | return (
5 |
8 | )
9 | }
10 |
11 | export default PaginationButton
12 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/components/ThemeToggle.tsx:
--------------------------------------------------------------------------------
1 | import { IconButton } from "@radix-ui/themes"
2 | import { FiSun, FiMoon } from "react-icons/fi"
3 | import { useTheme } from "../hooks/useTheme.js"
4 |
5 | export function ThemeToggle() {
6 | const { theme, toggleTheme } = useTheme()
7 |
8 | return (
9 |
10 |
16 | {theme === "light" ? : }
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/components/table/ColumnsDropdown.tsx:
--------------------------------------------------------------------------------
1 | import { Button, DropdownMenu } from "@radix-ui/themes"
2 | import { LuSlidersHorizontal } from "react-icons/lu"
3 | import { Table } from "@tanstack/react-table"
4 | import { ClickableChecklistItem } from "../ClickableChecklistItem.js"
5 |
6 | export const ColumnsDropdown = ({ tanStackTable }: { tanStackTable: Table }) => (
7 |
8 |
9 |
13 |
14 |
15 | {" "}
16 | {tanStackTable.getAllLeafColumns().map((column) => (
17 |
22 | {column.columnDef.header?.toString()}
23 |
24 | ))}
25 |
26 |
27 | )
28 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/components/table/SortSelector.tsx:
--------------------------------------------------------------------------------
1 | import { LuChevronDown, LuChevronUp, LuChevronsUpDown } from "react-icons/lu"
2 | import { Button, Flex } from "@radix-ui/themes"
3 | import { Header } from "@tanstack/react-table"
4 |
5 | export const SortSelector = ({ header }: { header: Header }) => (
6 |
7 |
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/hooks/useCursorStack.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | function useCursorStack() {
4 | const [cursors, setCursors] = useState([])
5 |
6 | const clearCursors = () => {
7 | setCursors([])
8 | }
9 |
10 | const pushCursor = (cursor: T) => {
11 | setCursors((cursors: T[]) => [...cursors, cursor])
12 | }
13 |
14 | const popCursor = () => {
15 | setCursors((cursors: T[]) => cursors.slice(0, -1))
16 | }
17 |
18 | const currentCursor = cursors[cursors.length - 1] || null
19 |
20 | return { clearCursors, currentCursor, pushCursor, popCursor }
21 | }
22 |
23 | export default useCursorStack
24 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/hooks/usePageTitle.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 |
3 | export const usePageTitle = (title: string) => {
4 | useEffect(() => {
5 | document.title = title
6 | }, [title])
7 | }
8 |
--------------------------------------------------------------------------------
/packages/network-explorer/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/network-explorer/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
--------------------------------------------------------------------------------
/packages/network-explorer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "module": "Node16",
6 | "moduleResolution": "Node16",
7 | "rootDir": "src",
8 | "outDir": "lib",
9 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
10 | "jsx": "react-jsx",
11 | "strict": true,
12 | "noUnusedLocals": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "resolveJsonModule": true
15 | },
16 | "references": []
17 | }
18 |
--------------------------------------------------------------------------------
/packages/network-explorer/tsconfig.vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src"],
3 | "compilerOptions": {
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "target": "es2020",
7 | "rootDir": "src",
8 | "outDir": "lib",
9 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
10 | "jsx": "react-jsx",
11 | "strict": true,
12 | "noUnusedLocals": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "resolveJsonModule": true,
15 | "composite": true,
16 | "declaration": true,
17 | "allowSyntheticDefaultImports": true,
18 | "skipLibCheck": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/relay-server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22-alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY lib lib
6 | COPY package.json package.json
7 | RUN npm install --omit-dev
8 |
9 | CMD ["npm", "run", "start"]
10 |
--------------------------------------------------------------------------------
/packages/relay-server/README.md:
--------------------------------------------------------------------------------
1 | # @canvas-js/relay-server
2 |
3 | The public multiaddr for the relay server is
4 |
5 | ```
6 | /dns4/canvas-relay-server.fly.dev/tcp/443/wss/p2p/12D3KooWLR64DxxPcW1vA6uyC74RYHEsoHwJEmCJRavTihLYmBZN
7 | ```
8 |
9 | ## Deploy
10 |
11 | ```
12 | $ fly deploy
13 | ```
14 |
15 | ## Development
16 |
17 | ```
18 | $ npm run start
19 | ```
20 |
--------------------------------------------------------------------------------
/packages/relay-server/create-peer-id.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { generateKeyPair, privateKeyToProtobuf } from "@libp2p/crypto/keys"
4 | import { peerIdFromPublicKey } from "@libp2p/peer-id"
5 |
6 | const privateKey = await generateKeyPair("Ed25519")
7 | const peerId = peerIdFromPublicKey(privateKey.publicKey)
8 | console.log(`# ${peerId}`)
9 | console.log(`LIBP2P_PRIVATE_KEY=${Buffer.from(privateKeyToProtobuf(privateKey)).toString("base64")}`)
10 |
--------------------------------------------------------------------------------
/packages/relay-server/deploy-mainnet.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | fly deploy --build-arg NODE_ENV=production --remote-only -a canvas-relay-server -c mainnet/fly.toml
3 |
--------------------------------------------------------------------------------
/packages/relay-server/deploy-testnet.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | fly deploy --build-arg NODE_ENV=production --remote-only -a canvas-relay-testnet -c testnet/fly.toml
3 |
--------------------------------------------------------------------------------
/packages/relay-server/mainnet/fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-relay-server'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | ANNOUNCE = '/dns4/canvas-relay-server.fly.dev/tcp/443/wss'
10 | DEBUG = 'libp2p:circuit-relay:*'
11 | LISTEN = '/ip4/0.0.0.0/tcp/8080/ws'
12 | PORT = '8000'
13 |
14 | [[services]]
15 | protocol = 'tcp'
16 | internal_port = 8080
17 | processes = ['app']
18 |
19 | [[services.ports]]
20 | port = 443
21 | handlers = ['tls', 'http']
22 |
23 | [services.concurrency]
24 | type = 'connections'
25 | hard_limit = 1024
26 | soft_limit = 512
27 |
28 | [[vm]]
29 | memory = '2gb'
30 | cpu_kind = 'shared'
31 | cpus = 2
32 |
33 | [[metrics]]
34 | port = 8000
35 | path = '/metrics'
36 |
--------------------------------------------------------------------------------
/packages/relay-server/src/api.ts:
--------------------------------------------------------------------------------
1 | import express, { Express } from "express"
2 | import { StatusCodes } from "http-status-codes"
3 | import { Libp2p } from "libp2p"
4 | import client from "prom-client"
5 |
6 | import { ServiceMap } from "./libp2p.js"
7 |
8 | export function createAPI(libp2p: Libp2p): Express {
9 | const app = express()
10 |
11 | app.get("/", (req, res) => {
12 | return void res.writeHead(StatusCodes.OK).end()
13 | })
14 |
15 | app.get("/metrics", async (req, res) => {
16 | try {
17 | const result = await client.register.metrics()
18 | return void res.writeHead(StatusCodes.OK, { "content-type": client.register.contentType }).end(result)
19 | } catch (err: any) {
20 | console.error(err)
21 | return void res.writeHead(StatusCodes.INTERNAL_SERVER_ERROR).end()
22 | }
23 | })
24 |
25 | return app
26 | }
27 |
--------------------------------------------------------------------------------
/packages/relay-server/src/index.ts:
--------------------------------------------------------------------------------
1 | import http from "node:http"
2 |
3 | import { createAPI } from "./api.js"
4 | import { getLibp2p } from "./libp2p.js"
5 |
6 | const { PORT = "8000", FLY_APP_NAME } = process.env
7 | const hostname = FLY_APP_NAME !== undefined ? `${FLY_APP_NAME}.internal` : "localhost"
8 |
9 | const libp2p = await getLibp2p()
10 |
11 | libp2p.addEventListener("start", async () => {
12 | console.log("libp2p started")
13 | })
14 |
15 | libp2p.addEventListener("stop", () => {
16 | console.log("libp2p stopped")
17 | })
18 |
19 | libp2p.addEventListener("connection:open", ({ detail: { remotePeer, remoteAddr } }) => {
20 | console.log(`connection:open ${remotePeer} ${remoteAddr}`)
21 | })
22 |
23 | libp2p.addEventListener("connection:close", ({ detail: { remotePeer, remoteAddr } }) => {
24 | console.log(`connection:close ${remotePeer} ${remoteAddr}`)
25 | })
26 |
27 | const api = createAPI(libp2p)
28 |
29 | http.createServer(api).listen(parseInt(PORT), () => {
30 | console.log(`HTTP API listening on http://${hostname}:${PORT}`)
31 | })
32 |
33 | await libp2p.start()
34 |
--------------------------------------------------------------------------------
/packages/relay-server/testnet/fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-relay-testnet'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | ANNOUNCE = '/dns6/canvas-relay-testnet.internal/tcp/8080/ws'
10 | DEBUG = 'libp2p:circuit-relay:*'
11 | LISTEN = '/ip4/0.0.0.0/tcp/8080/ws'
12 | PORT = '8000'
13 |
14 | [[vm]]
15 | memory = '2gb'
16 | cpu_kind = 'shared'
17 | cpus = 1
18 |
19 | [[metrics]]
20 | port = 8000
21 | path = '/metrics'
22 |
--------------------------------------------------------------------------------
/packages/relay-server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/signatures/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/signatures/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/signatures",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "sideEffects": false,
15 | "imports": {
16 | "#target": {
17 | "node": "./lib/targets/node/index.js",
18 | "browser": "./lib/targets/browser/index.js",
19 | "react-native": "./lib/targets/react-native/index.js",
20 | "default": "./lib/targets/default/index.js"
21 | }
22 | },
23 | "scripts": {
24 | "test": "ava"
25 | },
26 | "dependencies": {
27 | "@canvas-js/interfaces": "0.16.1",
28 | "@canvas-js/utils": "1.0.0",
29 | "@ipld/dag-cbor": "^9.2.4",
30 | "@ipld/dag-json": "^10.2.5",
31 | "@libp2p/logger": "^5.1.13",
32 | "@noble/curves": "^1.9.2",
33 | "@noble/hashes": "^1.8.0",
34 | "multiformats": "^13.3.7"
35 | },
36 | "devDependencies": {
37 | "react-native-mmkv": "^3.1.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/signatures/src/index.ts:
--------------------------------------------------------------------------------
1 | export { Ed25519SignatureScheme as ed25519 } from "./ed25519.js"
2 | export { SnapshotSignatureScheme, SnapshotSigner } from "./snapshot.js"
3 | export * from "./AbstractSessionSigner.js"
4 |
5 | export { encodeURI, decodeURI, prepareMessage } from "./utils.js"
6 |
--------------------------------------------------------------------------------
/packages/signatures/src/targets/default/index.ts:
--------------------------------------------------------------------------------
1 | import type { PlatformTarget } from "../index.js"
2 |
3 | export default {
4 | get(key: string): string | null {
5 | throw new Error("unsupported platform")
6 | },
7 | set(key: string, value: string) {
8 | throw new Error("unsupported platform")
9 | },
10 | clear(prefix?: string) {
11 | throw new Error("unsupported platform")
12 | },
13 | *keys(prefix?: string): IterableIterator {
14 | throw new Error("unsupported platform")
15 | },
16 | *entries(prefix?: string): IterableIterator<[string, string]> {
17 | throw new Error("unsupported platform")
18 | },
19 | getDomain(): string {
20 | throw new Error("unsupported platform")
21 | },
22 | sha256(input: Uint8Array): Uint8Array {
23 | throw new Error("unsupported platform")
24 | },
25 | } satisfies PlatformTarget
26 |
--------------------------------------------------------------------------------
/packages/signatures/src/targets/index.ts:
--------------------------------------------------------------------------------
1 | export interface PlatformTarget {
2 | get(key: string): string | null
3 | set(key: string, value: string): void
4 | clear(prefix?: string): void
5 |
6 | keys(prefix?: string): IterableIterator
7 | entries(prefix?: string): IterableIterator<[string, string]>
8 |
9 | getDomain(): string
10 |
11 | sha256(input: Uint8Array | Iterable): Uint8Array
12 | }
13 |
--------------------------------------------------------------------------------
/packages/signatures/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../." }, { "path": "../../interfaces" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signatures/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../interfaces" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-atp/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/signer-atp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/signer-atp",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
6 | "main": "lib/index.js",
7 | "types": "lib/index.d.ts",
8 | "sideEffects": false,
9 | "files": [
10 | "lib"
11 | ],
12 | "scripts": {
13 | "test": "ava",
14 | "generate-test-data": "node test/lib/generateTestData.js"
15 | },
16 | "dependencies": {
17 | "@atproto/api": "^0.14.7",
18 | "@atproto/crypto": "^0.4.4",
19 | "@canvas-js/interfaces": "0.16.1",
20 | "@canvas-js/signatures": "0.16.1",
21 | "@canvas-js/utils": "1.0.0",
22 | "@ipld/car": "^5.4.2",
23 | "@ipld/dag-cbor": "^9.2.4",
24 | "@ipld/dag-json": "^10.2.5",
25 | "@libp2p/logger": "^5.1.13",
26 | "@noble/hashes": "^1.8.0",
27 | "multiformats": "^13.3.7"
28 | },
29 | "devDependencies": {
30 | "@canvas-js/signer-atp": "0.16.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/signer-atp/src/commit.ts:
--------------------------------------------------------------------------------
1 | import { CID } from "multiformats"
2 | import * as cbor from "@ipld/dag-cbor"
3 | import { verifySignature } from "@atproto/crypto"
4 |
5 | import { assert } from "@canvas-js/utils"
6 |
7 | export type Commit = {
8 | did: string
9 | rev: string
10 | sig: Uint8Array
11 | data: CID
12 | prev: CID | null
13 | version: number
14 | }
15 |
16 | export async function verifyCommit(verificationMethod: string, commit: Commit) {
17 | const { sig, ...unsignedCommit } = commit
18 | await verifySignature(verificationMethod, cbor.encode(unsignedCommit), sig).then(assert)
19 | }
20 |
--------------------------------------------------------------------------------
/packages/signer-atp/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ATPSigner.js"
2 | export * from "./operation.js"
3 |
--------------------------------------------------------------------------------
/packages/signer-atp/test/README.md:
--------------------------------------------------------------------------------
1 | # `signer-atp` tests
2 |
3 | These tests make use of test fixtures that have been captured from responses from a Bluesky PDS and the [DID PLC Directory](https://web.plc.directory/).
4 |
5 | To update these test fixtures, run the command `npm run generate-test-data` from inside the `signer-atp` package (or from the root of this monorepo using `npm run generate-test-data -w=packages/signer-atp`). When prompted, enter the identifier (e.g. `somebody.bsky.social`) and an app password of a Bluesky account. The script makes a number of changes to the fixture files, which should be checked into the repository:
6 |
7 | - `test/fixture.json` is updated
8 | - `test/plcOperationLog.json` is updated
9 | - A new file `test/archives/.car` is created. The other files in the archives directory should be deleted.
10 |
11 | To confirm that the fixture update has worked, run `npm run test` for this package.
12 |
--------------------------------------------------------------------------------
/packages/signer-atp/test/archives/3kgvb53cuh22l.car:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/packages/signer-atp/test/archives/3kgvb53cuh22l.car
--------------------------------------------------------------------------------
/packages/signer-atp/test/fixture.json:
--------------------------------------------------------------------------------
1 | {
2 | "did": "did:plc:uh3qgppih5ocj6hsvtlsg5v7",
3 | "signingKey": "did:key:zQ3shkEorJBU14FH9L1eLfU6QdE1FBhA7zC75XbrDLYWETEeJ",
4 | "topic": "a7fbe055-2139-4f26-9868-ebbd7ed80a64",
5 | "archives": {
6 | "test/archives/3kgvb53cuh22l.car": "at://did:plc:uh3qgppih5ocj6hsvtlsg5v7/app.bsky.feed.post/3kgvb53cuh22l"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/signer-atp/test/plcOperationLog.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "sig": "5DaCy05m4TO2ENm2I0yUTHLMXfFQyJnKILFgo7P6rcxzhH8DdzZcH6T4XwRTBuxZTttOb7vTiUvOSOqhJO0GIQ",
4 | "prev": null,
5 | "type": "plc_operation",
6 | "services": {
7 | "atproto_pds": {
8 | "type": "AtprotoPersonalDataServer",
9 | "endpoint": "https://morel.us-east.host.bsky.network"
10 | }
11 | },
12 | "alsoKnownAs": ["at://canvastest.bsky.social"],
13 | "rotationKeys": [
14 | "did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg",
15 | "did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK"
16 | ],
17 | "verificationMethods": {
18 | "atproto": "did:key:zQ3shRNshw8JrhfbVKiBML9aDvysaSRbzHwiVTtUycN8YcqWd"
19 | }
20 | }
21 | ]
22 |
--------------------------------------------------------------------------------
/packages/signer-atp/test/signer.test.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs"
2 |
3 | import { ATPSigner, verifyLog } from "@canvas-js/signer-atp"
4 |
5 | import test from "ava"
6 |
7 | const { topic, did, signingKey, archives } = JSON.parse(fs.readFileSync("test/fixture.json", "utf-8"))
8 |
9 | const plcOperationLog = JSON.parse(fs.readFileSync("test/plcOperationLog.json", "utf-8"))
10 |
11 | test("create and verify session", async (t) => {
12 | const signer = new ATPSigner()
13 |
14 | const verificationMethod = await verifyLog(did, plcOperationLog)
15 |
16 | for (const [path, uri] of Object.entries(archives)) {
17 | const recordArchive = fs.readFileSync(path)
18 | await signer.verifySession(topic, {
19 | type: "session",
20 |
21 | did: did,
22 | publicKey: signingKey,
23 |
24 | authorizationData: {
25 | verificationMethod: verificationMethod,
26 | recordArchive,
27 | recordURI: uri as string,
28 | plcOperationLog: plcOperationLog,
29 | },
30 |
31 | context: {
32 | timestamp: Date.now(),
33 | },
34 | })
35 | }
36 |
37 | t.pass()
38 | })
39 |
--------------------------------------------------------------------------------
/packages/signer-atp/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib",
7 | "module": "Node16",
8 | "moduleResolution": "Node16"
9 | },
10 | "references": [{ "path": "../." }, { "path": "../../interfaces" }]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/signer-atp/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../interfaces" }, { "path": "../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-cosmos/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/signer-cosmos",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
6 | "main": "lib/index.js",
7 | "types": "lib/index.d.ts",
8 | "sideEffects": false,
9 | "files": [
10 | "lib"
11 | ],
12 | "scripts": {
13 | "test": "ava"
14 | },
15 | "dependencies": {
16 | "@canvas-js/interfaces": "0.16.1",
17 | "@canvas-js/signatures": "0.16.1",
18 | "@canvas-js/utils": "1.0.0",
19 | "@cosmjs/amino": "^0.32.3",
20 | "@cosmjs/crypto": "^0.32.3",
21 | "@cosmjs/encoding": "^0.32.3",
22 | "@ipld/dag-cbor": "^9.2.4",
23 | "@ipld/dag-json": "^10.2.5",
24 | "@keplr-wallet/types": "^0.11.64",
25 | "@libp2p/logger": "^5.1.13",
26 | "@noble/curves": "^1.9.2",
27 | "@noble/hashes": "^1.8.0",
28 | "ethers": "^6.13.5",
29 | "multiformats": "^13.3.7"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/signer-cosmos/src/external_signers/default.ts:
--------------------------------------------------------------------------------
1 | import { toBech32 } from "@cosmjs/encoding"
2 | import { hexToBytes } from "@noble/hashes/utils"
3 | import { Wallet } from "ethers"
4 |
5 | import { CosmosMessage } from "../types.js"
6 | import { constructSiwxMessage } from "../utils.js"
7 |
8 | export const createDefaultSigner = (bech32Prefix: string) => {
9 | const wallet = Wallet.createRandom()
10 | return {
11 | // this wallet is not associated with any chain
12 | getChainId: async () => "no_chain-id-100",
13 | getAddress: async () => toBech32(bech32Prefix, hexToBytes(wallet.address.substring(2))),
14 | sign: async (cosmosMessage: CosmosMessage) => {
15 | const msg = constructSiwxMessage(cosmosMessage)
16 | const hexSignature = (await wallet.signMessage(msg)).substring(2)
17 | return {
18 | signature: hexToBytes(hexSignature),
19 | signatureType: "ethereum" as const,
20 | }
21 | },
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/signer-cosmos/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./CosmosSigner.js"
2 |
--------------------------------------------------------------------------------
/packages/signer-cosmos/src/signatureData.ts:
--------------------------------------------------------------------------------
1 | import type { AminoMsg, StdFee } from "@cosmjs/amino"
2 | import { makeSignDoc } from "@cosmjs/amino"
3 | import { toBase64 } from "@cosmjs/encoding"
4 |
5 | export const getSessionSignatureData = (sessionPayload: Uint8Array, address: string, chain_id?: string) => {
6 | const accountNumber = 0
7 | const sequence = 0
8 | const chainId = chain_id ?? ""
9 | const fee: StdFee = {
10 | gas: "0",
11 | amount: [],
12 | }
13 | const memo = ""
14 |
15 | const jsonTx: AminoMsg = {
16 | type: "sign/MsgSignData",
17 | value: {
18 | signer: address,
19 | data: toBase64(sessionPayload),
20 | },
21 | }
22 | const signDoc = makeSignDoc([jsonTx], fee, chainId, memo, accountNumber, sequence)
23 | return signDoc
24 | }
25 |
--------------------------------------------------------------------------------
/packages/signer-cosmos/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { EthereumSignedSessionData, EthereumSigner } from "./external_signers/ethereum.js"
2 | import type { BytesSignedSessionData, BytesSigner } from "./external_signers/bytes.js"
3 | import type { AminoSignedSessionData, AminoSigner } from "./external_signers/amino.js"
4 | import { ArbitrarySignedSessionData, ArbitrarySigner } from "./external_signers/arbitrary.js"
5 |
6 | export type CosmosMessage = {
7 | topic: string
8 | address: string
9 | chainId: string
10 | publicKey: string
11 | issuedAt: string
12 | expirationTime: string | null
13 | }
14 |
15 | export type CosmosSessionData =
16 | | EthereumSignedSessionData
17 | | BytesSignedSessionData
18 | | AminoSignedSessionData
19 | | ArbitrarySignedSessionData
20 |
21 | export type ExternalCosmosSigner = EthereumSigner | AminoSigner | BytesSigner | ArbitrarySigner
22 |
--------------------------------------------------------------------------------
/packages/signer-cosmos/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../." }, { "path": "../../interfaces" }, { "path": "../../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-cosmos/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../signatures" }, { "path": "../interfaces" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-ethereum-viem/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/signer-ethereum-viem/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/signer-ethereum-viem",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
6 | "main": "lib/index.js",
7 | "types": "lib/index.d.ts",
8 | "sideEffects": false,
9 | "files": [
10 | "lib"
11 | ],
12 | "scripts": {
13 | "test": "ava"
14 | },
15 | "devDependencies": {
16 | "@canvas-js/signer-ethereum-viem": "0.16.1"
17 | },
18 | "dependencies": {
19 | "@canvas-js/interfaces": "0.16.1",
20 | "@canvas-js/signatures": "0.16.1",
21 | "@canvas-js/utils": "1.0.0",
22 | "@ipld/dag-json": "^10.2.5",
23 | "@libp2p/logger": "^5.1.13",
24 | "multiformats": "^13.3.7",
25 | "siwe": "^3.0.0",
26 | "viem": "^2.22.21"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/signer-ethereum-viem/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SIWESignerViem.js"
2 | export { validateSessionData } from "./utils.js"
3 |
--------------------------------------------------------------------------------
/packages/signer-ethereum-viem/src/types.ts:
--------------------------------------------------------------------------------
1 | export type SIWESessionData = {
2 | signature: Uint8Array
3 | domain: string
4 | nonce: string
5 | }
6 |
7 | export type SIWEMessage = {
8 | version: string
9 | address: string
10 | chainId: number
11 | domain: string
12 | uri: string
13 | nonce: string
14 | issuedAt: string
15 | expirationTime: string | null
16 | resources: string[]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/signer-ethereum-viem/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../." }, { "path": "../../interfaces" }, { "path": "../../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-ethereum-viem/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../interfaces" }, { "path": "../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/signer-ethereum",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
6 | "main": "lib/index.js",
7 | "types": "lib/index.d.ts",
8 | "sideEffects": false,
9 | "files": [
10 | "lib"
11 | ],
12 | "scripts": {
13 | "test": "ava"
14 | },
15 | "dependencies": {
16 | "@canvas-js/interfaces": "0.16.1",
17 | "@canvas-js/signatures": "0.16.1",
18 | "@canvas-js/utils": "1.0.0",
19 | "@ipld/dag-json": "^10.2.5",
20 | "@libp2p/logger": "^5.1.13",
21 | "ethers": "^6.13.5",
22 | "multiformats": "^13.3.7",
23 | "siwe": "^3.0.0"
24 | },
25 | "devDependencies": {
26 | "base58-solidity": "^1.0.2"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/src/eip712/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Eip712Signer.js"
2 | export * from "./Secp256k1DelegateSigner.js"
3 | export { validateEip712SessionData } from "./utils.js"
4 | export { Eip712SessionData } from "./types.js"
5 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/src/eip712/types.ts:
--------------------------------------------------------------------------------
1 | export type Eip712SessionData = {
2 | signature: Uint8Array
3 | }
4 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/src/eip712/utils.ts:
--------------------------------------------------------------------------------
1 | import { assert } from "@canvas-js/utils"
2 | import { Eip712SessionData } from "./types.js"
3 |
4 | export const addressPattern = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]+)$/
5 |
6 | export function parseAddress(address: string): { chainId: number; address: `0x${string}` } {
7 | const result = addressPattern.exec(address)
8 | assert(result !== null)
9 | const [_, chainIdResult, addressResult] = result
10 | return { chainId: parseInt(chainIdResult), address: addressResult as `0x${string}` }
11 | }
12 |
13 | export function validateEip712SessionData(authorizationData: unknown): authorizationData is Eip712SessionData {
14 | try {
15 | const { signature } = authorizationData as any
16 | assert(signature instanceof Uint8Array, "signature must be a Uint8Array")
17 | return true
18 | } catch (e) {
19 | return false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./siwe/index.js"
2 | export * from "./siwf/index.js"
3 | export * from "./eip712/index.js"
4 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/src/siwe/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SIWESigner.js"
2 | export { SIWESessionData } from "./types.js"
3 | export { validateSIWESessionData } from "./utils.js"
4 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/src/siwe/types.ts:
--------------------------------------------------------------------------------
1 | export type SIWESessionData = {
2 | signature: Uint8Array
3 | domain: string
4 | nonce: string
5 | }
6 |
7 | export type SIWEMessage = {
8 | version: string
9 | address: string
10 | chainId: number
11 | domain: string
12 | uri: string
13 | nonce: string
14 | issuedAt: string
15 | expirationTime: string | null
16 | resources: string[]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/src/siwf/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SIWFSigner.js"
2 | export { SIWFSessionData } from "./types.js"
3 | export { validateSIWFSessionData } from "./utils.js"
4 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/src/siwf/types.ts:
--------------------------------------------------------------------------------
1 | export type SIWFSessionData = {
2 | custodyAddress: string
3 | fid: string
4 | signature: Uint8Array
5 | siweUri: string
6 | siweDomain: string
7 | siweNonce: string
8 | siweVersion: string
9 | siweChainId: number
10 | siweIssuedAt: string
11 | siweExpirationTime: string | null
12 | siweNotBefore: string | null
13 | frame?: true
14 | }
15 |
16 | export type SIWFMessage = {
17 | version: string
18 | address: string
19 | chainId: number
20 | domain: string
21 | uri: string
22 | nonce: string
23 | issuedAt: string
24 | expirationTime: string | undefined
25 | notBefore: string | undefined
26 | requestId: string
27 | resources: string[]
28 | }
29 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../." }, { "path": "../../interfaces" }, { "path": "../../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-ethereum/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../interfaces" }, { "path": "../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-solana/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/signer-solana/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/signer-solana",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
6 | "main": "lib/index.js",
7 | "types": "lib/index.d.ts",
8 | "sideEffects": false,
9 | "files": [
10 | "lib"
11 | ],
12 | "scripts": {
13 | "test": "ava"
14 | },
15 | "dependencies": {
16 | "@canvas-js/interfaces": "0.16.1",
17 | "@canvas-js/signatures": "0.16.1",
18 | "@canvas-js/utils": "1.0.0",
19 | "@ipld/dag-json": "^10.2.5",
20 | "@libp2p/logger": "^5.1.13",
21 | "@noble/curves": "^1.9.2",
22 | "@solana/web3.js": "^1.93.0",
23 | "multiformats": "^13.3.7"
24 | },
25 | "devDependencies": {
26 | "@canvas-js/signer-solana": "0.16.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/signer-solana/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SolanaSigner.js"
2 | export { validateSessionData } from "./utils.js"
3 |
--------------------------------------------------------------------------------
/packages/signer-solana/src/types.ts:
--------------------------------------------------------------------------------
1 | export type SolanaSessionData = {
2 | signature: Uint8Array
3 | }
4 |
5 | export type SolanaMessage = {
6 | chainId: string
7 | address: string
8 | topic: string
9 | publicKey: string
10 | issuedAt: string
11 | expirationTime: string | null
12 | }
13 |
--------------------------------------------------------------------------------
/packages/signer-solana/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../." }, { "path": "../../interfaces" }, { "path": "../../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-solana/test/utils.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canvasxyz/canvas/4607acdc2fcae20f24a9249323ce4d5e24003527/packages/signer-solana/test/utils.ts
--------------------------------------------------------------------------------
/packages/signer-solana/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../interfaces" }, { "path": "../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-substrate/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/signer-substrate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/signer-substrate",
3 | "version": "0.16.1",
4 | "type": "module",
5 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
6 | "main": "lib/index.js",
7 | "types": "lib/index.d.ts",
8 | "sideEffects": false,
9 | "files": [
10 | "lib"
11 | ],
12 | "scripts": {
13 | "test": "ava"
14 | },
15 | "dependencies": {
16 | "@canvas-js/interfaces": "0.16.1",
17 | "@canvas-js/signatures": "0.16.1",
18 | "@canvas-js/utils": "1.0.0",
19 | "@ipld/dag-json": "^10.2.5",
20 | "@libp2p/logger": "^5.1.13",
21 | "@noble/hashes": "^1.8.0",
22 | "@polkadot/extension-inject": "^0.46.3",
23 | "@polkadot/keyring": "^12.6.2",
24 | "@polkadot/util-crypto": "^12.2.1",
25 | "multiformats": "^13.3.7"
26 | },
27 | "devDependencies": {
28 | "@polkadot/types": "^10.7.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/signer-substrate/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SubstrateSigner.js"
2 | export { validateSessionData } from "./utils.js"
3 |
--------------------------------------------------------------------------------
/packages/signer-substrate/src/types.ts:
--------------------------------------------------------------------------------
1 | import { KeypairType } from "@polkadot/util-crypto/types"
2 |
3 | export type SubstrateSessionData = {
4 | signatureResult: {
5 | signature: Uint8Array
6 | nonce: Uint8Array
7 | }
8 | substrateKeyType: KeypairType
9 | data: SubstrateMessage
10 | }
11 |
12 | export type SubstrateMessage = {
13 | topic: string
14 | address: string
15 | chainId: string
16 | uri: string
17 | issuedAt: string
18 | expirationTime: string | null
19 | }
20 |
--------------------------------------------------------------------------------
/packages/signer-substrate/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../." }, { "path": "../../interfaces" }, { "path": "../../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/signer-substrate/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../interfaces" }, { "path": "../signatures" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/test-network/.dockerignore:
--------------------------------------------------------------------------------
1 | data/
--------------------------------------------------------------------------------
/packages/test-network/.gitignore:
--------------------------------------------------------------------------------
1 | data/
2 | docker-compose.yml
3 |
--------------------------------------------------------------------------------
/packages/test-network/README.md:
--------------------------------------------------------------------------------
1 | # @canvas-js/test-network
2 |
3 | First build the dashboard client bundles
4 |
5 | ```
6 | $ npm run dev
7 | ```
8 |
9 | Then start the docker containers
10 |
11 | ```
12 | $ npm run start
13 | ```
14 |
15 | Open http://localhost:8000 to view the dashboard.
16 |
17 | Configure the network by creating a .env file
18 |
19 | ```
20 | NUM_TOPICS=1
21 | NUM_PEERS=8
22 | DELAY=10
23 | INTERVAL=10
24 | DEBUG=canvas:*
25 | ```
26 |
27 | To test WebRTC clients, set NUM_PEERS to 0 in .env and run both `npm run start` and `spawn.sh` in separate terminals.
28 |
--------------------------------------------------------------------------------
/packages/test-network/client-libp2p/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 | Browser client (WebRTC)
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/packages/test-network/client-libp2p/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib",
7 | "jsx": "react"
8 | },
9 | "references": [{ "path": "../../../gossiplog" }, { "path": "../.." }]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/test-network/client-libp2p/fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-testnet-client-libp2p'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | DASHBOARD_URL = 'http://canvas-testnet-dashboard.internal:8000'
10 | RELAY_SERVER = '/dns6/canvas-testnet-relay.internal/tcp/8080/ws/p2p/12D3KooWNnETFoRgEPW9uYzHLERY7iQR6JHZs12NUzruqXjHdEhr'
11 | BOOTSTRAP_SERVER = '/dns6/canvas-testnet-rendezvous.internal/tcp/8080/ws/p2p/12D3KooWBKPzBJJKgfq5newmWLUR1ReVQfgrjkyGBSYBQ5CMVsXC'
12 |
13 | [[vm]]
14 | memory = '2gb'
15 | cpu_kind = 'shared'
16 | cpus = 2
17 |
--------------------------------------------------------------------------------
/packages/test-network/client-libp2p/worker/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../.." }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/test-network/client-ws/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 | Browser client (WebSocket)
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/packages/test-network/client-ws/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib",
7 | "jsx": "react"
8 | },
9 | "references": [{ "path": "../../../gossiplog" }, { "path": "../.." }]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/test-network/client-ws/worker/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../.." }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/test-network/create-peer-id.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { generateKeyPair, privateKeyToProtobuf } from "@libp2p/crypto/keys"
4 | import { peerIdFromPublicKey } from "@libp2p/peer-id"
5 |
6 | const privateKey = await generateKeyPair("Ed25519")
7 | const peerId = peerIdFromPublicKey(privateKey.publicKey)
8 | console.log(`# ${peerId}`)
9 | console.log(`LIBP2P_PRIVATE_KEY=${Buffer.from(privateKeyToProtobuf(privateKey)).toString("base64")}`)
10 |
--------------------------------------------------------------------------------
/packages/test-network/dashboard/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22-alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY server/dist/index.js index.mjs
6 | COPY client/dist dist/
7 |
8 | ENTRYPOINT ["node"]
9 | CMD ["index.mjs"]
10 |
--------------------------------------------------------------------------------
/packages/test-network/dashboard/client/src/EventLog.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { PeerEvent } from "@canvas-js/test-network/events"
3 |
4 | export const EventLog: React.FC<{ events: PeerEvent[] }> = ({ events }) => {
5 | const [visible, setVisible] = useState(false)
6 |
7 | if (visible) {
8 | return (
9 | <>
10 |
11 |
12 | {events.map((event, index) => {
13 | if (event === null) {
14 | return null
15 | }
16 |
17 | const { type, peerId, timestamp, detail } = event
18 | const time = new Date(timestamp).toISOString().slice(11, -1)
19 | return (
20 |
21 |
22 | [{time}] [{peerId}] {type} {JSON.stringify(detail, null, " ")}
23 |
24 |
25 | )
26 | })}
27 |
28 | >
29 | )
30 | } else {
31 | return
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/test-network/dashboard/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { createRoot } from "react-dom/client"
3 |
4 | import { App } from "./App.js"
5 |
6 | const element = document.getElementById("root")!
7 | const root = createRoot(element)
8 | root.render()
9 |
--------------------------------------------------------------------------------
/packages/test-network/dashboard/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib",
7 | "jsx": "react"
8 | },
9 | "references": []
10 | }
11 |
--------------------------------------------------------------------------------
/packages/test-network/dashboard/fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-testnet-dashboard'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | PORT = '8000'
10 |
11 | [[vm]]
12 | memory = '2gb'
13 | cpu_kind = 'shared'
14 | cpus = 2
15 |
--------------------------------------------------------------------------------
/packages/test-network/dashboard/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": []
9 | }
10 |
--------------------------------------------------------------------------------
/packages/test-network/relay/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22-alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY dist/index.js lib/index.mjs
6 |
7 | ENTRYPOINT ["node"]
8 | CMD ["lib/index.mjs"]
9 |
--------------------------------------------------------------------------------
/packages/test-network/relay/fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-testnet-relay'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | LISTEN = '/ip6/::/tcp/8080/ws'
10 | ANNOUNCE = '/dns6/canvas-testnet-relay.internal/tcp/8080/ws'
11 |
12 | [[vm]]
13 | memory = '2gb'
14 | cpu_kind = 'shared'
15 | cpus = 2
16 |
--------------------------------------------------------------------------------
/packages/test-network/relay/src/index.ts:
--------------------------------------------------------------------------------
1 | import { getLibp2p } from "@canvas-js/relay-server/libp2p"
2 |
3 | const libp2p = await getLibp2p({})
4 |
5 | await libp2p.start()
6 |
7 | libp2p.addEventListener("connection:open", ({ detail: { remotePeer, remoteAddr } }) => {
8 | console.log(`connection:open ${remotePeer} ${remoteAddr}`)
9 | })
10 |
11 | libp2p.addEventListener("connection:close", ({ detail: { remotePeer, remoteAddr } }) => {
12 | console.log(`connection:close ${remotePeer} ${remoteAddr}`)
13 | })
14 |
15 | libp2p.services.circuitRelay.addEventListener("relay:reservation", ({ detail }) => {
16 | console.log("relay:reservation", detail.addr.toString())
17 | })
18 |
--------------------------------------------------------------------------------
/packages/test-network/relay/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../../relay-server" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/test-network/rendezvous/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22-alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY dist/index.js lib/index.mjs
6 |
7 | RUN npm i -g better-sqlite3@11.10.0
8 | RUN npm link better-sqlite3
9 |
10 | ENTRYPOINT ["node"]
11 | CMD ["lib/index.mjs"]
12 |
--------------------------------------------------------------------------------
/packages/test-network/rendezvous/fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-testnet-rendezvous'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | PORT = '8000'
10 | LISTEN = '/ip6/::/tcp/8080/ws'
11 | ANNOUNCE = '/dns6/canvas-testnet-rendezvous.internal/tcp/8080/ws'
12 | DASHBOARD_URL = 'http://canvas-testnet-dashboard.internal:8000'
13 |
14 | [[vm]]
15 | memory = '2gb'
16 | cpu_kind = 'shared'
17 | cpus = 2
18 |
--------------------------------------------------------------------------------
/packages/test-network/rendezvous/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../../bootstrap-peer" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/test-network/scripts/spawn.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Number of processes
4 | PROCESS_COUNT="${1:-1}"
5 |
6 | # Peers per process
7 | export PEER_COUNT="${2:-10}"
8 | export DELAY=12
9 |
10 | echo "starting $PROCESS_COUNT processes of $PEER_COUNT peers"
11 |
12 | # command="node client-libp2p/worker/lib/index.js"
13 | command="node client-libp2p/worker/dist/index.js"
14 |
15 | for i in $(seq 1 $PROCESS_COUNT)
16 | do
17 | # Start the command and prefix its output
18 | $command | sed "s/^/[browser-$i] /" &
19 | done
20 |
21 | # Wait for all background processes to finish
22 | wait
23 | echo "All instances completed."
24 |
--------------------------------------------------------------------------------
/packages/test-network/scripts/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for APP in canvas-testnet-dashboard canvas-testnet-rendezvous canvas-testnet-relay canvas-testnet-client-libp2p; do
4 | echo "starting all ${APP} machines"
5 | fly machines list -a $APP --json | jq -r '.[] | .id' | while read MACHINE_ID; do
6 | fly machines start -a $APP $MACHINE_ID
7 | done
8 | done
9 |
--------------------------------------------------------------------------------
/packages/test-network/scripts/stop.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for APP in canvas-testnet-dashboard canvas-testnet-rendezvous canvas-testnet-relay canvas-testnet-client-libp2p; do
4 | echo "stopping all ${APP} machines"
5 | fly machines list -a $APP --json | jq -r '.[] | .id' | while read MACHINE_ID; do
6 | fly machines stop -a $APP $MACHINE_ID
7 | done
8 | done
9 |
--------------------------------------------------------------------------------
/packages/test-network/server-libp2p/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22-alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY dist/index.js lib/index.mjs
6 |
7 | RUN npm i -g @canvas-js/okra-lmdb@0.2.4
8 | RUN npm link @canvas-js/okra-lmdb
9 |
10 | RUN npm i -g better-sqlite3@11.10.0
11 | RUN npm link better-sqlite3
12 |
13 | ENTRYPOINT ["node"]
14 | CMD ["lib/index.mjs"]
15 |
--------------------------------------------------------------------------------
/packages/test-network/server-libp2p/fly.toml:
--------------------------------------------------------------------------------
1 | app = 'canvas-testnet-server-libp2p'
2 | primary_region = 'ewr'
3 | kill_signal = 'SIGINT'
4 | kill_timeout = '30s'
5 |
6 | [build]
7 |
8 | [env]
9 | DASHBOARD_URL = 'http://canvas-testnet-dashboard.internal:8000'
10 | BOOTSTRAP_LIST = '/dns6/canvas-testnet-rendezvous.internal/tcp/8080/ws/p2p/12D3KooWBKPzBJJKgfq5newmWLUR1ReVQfgrjkyGBSYBQ5CMVsXC'
11 |
12 | [[vm]]
13 | memory = '1gb'
14 | cpu_kind = 'shared'
15 | cpus = 1
16 |
--------------------------------------------------------------------------------
/packages/test-network/server-libp2p/src/config.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert"
2 |
3 | import { multiaddr } from "@multiformats/multiaddr"
4 |
5 | const { BOOTSTRAP_LIST, LISTEN, ANNOUNCE, TOPIC, DELAY = "0", INTERVAL = "0" } = process.env
6 |
7 | export const delay = parseInt(DELAY) * 1000
8 | export const interval = parseInt(INTERVAL) * 1000
9 |
10 | export const bootstrapList = BOOTSTRAP_LIST?.split(" ") ?? []
11 |
12 | assert(typeof TOPIC === "string")
13 | export const topic = TOPIC
14 |
15 | for (const address of bootstrapList) {
16 | const ma = multiaddr(address)
17 | const peerId = ma.getPeerId()
18 |
19 | if (peerId === null) {
20 | throw new Error("Invalid bootstrap peer address: must identify peer id using /p2p")
21 | }
22 | }
23 |
24 | export const listen = LISTEN?.split(" ") ?? ["/ip4/0.0.0.0/tcp/8080/ws"]
25 |
26 | console.log("listening on", listen)
27 |
28 | export const announce = ANNOUNCE?.split(" ") ?? []
29 |
30 | console.log("announcing on", announce)
31 |
--------------------------------------------------------------------------------
/packages/test-network/server-libp2p/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../../gossiplog" }, { "path": ".." }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/test-network/server-ws/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22-alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY dist/index.js lib/index.mjs
6 |
7 | RUN npm i -g @canvas-js/okra-lmdb@0.2.4
8 | RUN npm link @canvas-js/okra-lmdb
9 |
10 | RUN npm i -g better-sqlite3@11.10.0
11 | RUN npm link better-sqlite3
12 |
13 | ENTRYPOINT ["node"]
14 | CMD ["lib/index.mjs"]
15 |
--------------------------------------------------------------------------------
/packages/test-network/server-ws/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../../gossiplog" }, { "path": ".." }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/test-network/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const topic = "test-network-example"
2 |
--------------------------------------------------------------------------------
/packages/test-network/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib",
7 | "jsx": "react"
8 | },
9 | "references": [
10 | { "path": "../bootstrap-peer" },
11 | { "path": "../gossiplog" },
12 | { "path": "../interfaces" },
13 | { "path": "../relay-server" }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vm/README.md:
--------------------------------------------------------------------------------
1 | # @canvas-js/vm
2 |
--------------------------------------------------------------------------------
/packages/vm/ava.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | files: ["./test/*.test.ts"],
3 | concurrency: 1,
4 | typescript: {
5 | compile: false,
6 | rewritePaths: {
7 | "test/": "test/lib/",
8 | },
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@canvas-js/vm",
3 | "version": "0.16.1",
4 | "author": "Canvas Technologies, Inc. (https://canvas.xyz)",
5 | "type": "module",
6 | "engines": {
7 | "node": ">=22.0.0"
8 | },
9 | "files": [
10 | "lib"
11 | ],
12 | "main": "./lib/index.js",
13 | "types": "./lib/index.d.ts",
14 | "exports": {
15 | ".": "./lib/index.js"
16 | },
17 | "sideEffects": false,
18 | "scripts": {
19 | "test": "ava"
20 | },
21 | "dependencies": {
22 | "@canvas-js/utils": "1.0.0",
23 | "@libp2p/logger": "^5.1.13",
24 | "@noble/hashes": "^1.8.0",
25 | "quickjs-emscripten": "^0.31.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/vm/src/error.ts:
--------------------------------------------------------------------------------
1 | export class VMError extends Error {
2 | constructor(private readonly err: { name: string; message: string; stack: string }) {
3 | super(`[${err.name}: ${err.message}]`)
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/vm/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./vm.js"
2 | export * from "./error.js"
3 |
4 | import { VMError } from "./error.js"
5 | export { VMError }
6 |
--------------------------------------------------------------------------------
/packages/vm/test/errors.test.ts:
--------------------------------------------------------------------------------
1 | import test from "ava"
2 |
3 | import { VM } from "@canvas-js/vm"
4 |
5 | test("wrap and unwrap an error", async (t) => {
6 | const vm = await VM.initialize()
7 | t.teardown(() => vm.dispose())
8 | t.pass()
9 |
10 | // const errors = [
11 | // new Error("regular error"),
12 | // new TypeError("type error"),
13 | // new RangeError("range error"),
14 | // new SyntaxError("syntax error"),
15 | // ]
16 |
17 | // // TODO: whats going on here???
18 | // for (const error of errors) {
19 | // const handle = vm.wrapError({ name: error.name, message: error.message })
20 | // t.deepEqual(handle.consume(vm.unwrapError), error)
21 | // }
22 | })
23 |
--------------------------------------------------------------------------------
/packages/vm/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["lib"],
3 | "extends": "../../../tsconfig.base.json",
4 | "compilerOptions": {
5 | "rootDir": ".",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../." }, { "path": "../../interfaces" }]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/vm/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | },
8 | "references": [{ "path": "../interfaces" }]
9 | }
10 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "packages/*"
3 | - "examples/*"
4 | - "contracts"
5 |
--------------------------------------------------------------------------------
/scripts/decodeMessageId.js:
--------------------------------------------------------------------------------
1 | #!/bin/node
2 | const { bytesToHex } = await import("@noble/hashes/utils")
3 | const { MessageId } = await import("@canvas-js/gossiplog")
4 | const { base32hex } = await import("multiformats/bases/base32")
5 |
6 | const msgId = process.argv[process.argv.length - 1]
7 | const result = MessageId.decode(base32hex.baseDecode(msgId))
8 |
9 | console.log({ ...result, key: bytesToHex(result.key) })
10 |
--------------------------------------------------------------------------------
/scripts/generateLibp2pPrivkey.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { generateKeyPair, privateKeyToProtobuf } from "@libp2p/crypto/keys"
4 | import { peerIdFromPublicKey } from "@libp2p/peer-id"
5 |
6 | const privateKey = await generateKeyPair("Ed25519")
7 | const peerId = peerIdFromPublicKey(privateKey.publicKey)
8 | console.log(`# ${peerId}`)
9 | console.log(`LIBP2P_PRIVATE_KEY=${Buffer.from(privateKeyToProtobuf(privateKey)).toString("base64")}`)
10 |
--------------------------------------------------------------------------------
/scripts/setupPostgresTestEnv.cjs:
--------------------------------------------------------------------------------
1 | // Set up Postgres database for tests.
2 | //
3 | // This requires a user called postgres, with the ability to create databases.
4 | // If you are running it in a development environment, you can use:
5 | // `createuser postgres --createdb`
6 |
7 | // eslint-disable-next-line @typescript-eslint/no-require-imports
8 | const { Client } = require("pg")
9 |
10 | const pgclient = new Client({
11 | host: process.env.POSTGRES_HOST,
12 | port: process.env.POSTGRES_PORT,
13 | user: "postgres",
14 | password: "postgres",
15 | database: "postgres",
16 | })
17 |
18 | pgclient.connect()
19 |
20 | // Nest each query because otherwise pg.Client treats them as a transaction
21 | pgclient.query("CREATE DATABASE test", (err, res) => {
22 | if (err) throw err
23 | pgclient.query("CREATE DATABASE test2", (err2, res2) => {
24 | if (err2) throw err2
25 | pgclient.query("CREATE DATABASE test3", (err3, res3) => {
26 | if (err3) throw err3
27 | pgclient.end()
28 | })
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "strict": true,
5 | "declaration": true,
6 | "module": "node16",
7 | "target": "es2022",
8 | "moduleResolution": "node16",
9 | "allowSyntheticDefaultImports": true,
10 | "resolveJsonModule": true,
11 | "useDefineForClassFields": true,
12 | "skipLibCheck": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/uninstall.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # Uninstall the local development build of the Canvas CLI
4 | # from /usr/bin/dev.
5 |
6 | INSTALL_PATH=$(npm config get prefix)/bin/canvas
7 |
8 | if test -f "$INSTALL_PATH"; then
9 | rm ${INSTALL_PATH}
10 | echo "Uninstalled"
11 | else
12 | echo "No executable found"
13 | fi
14 |
--------------------------------------------------------------------------------
/upgrade.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Check if an argument is provided
4 | if [ $# -eq 0 ]; then
5 | echo "Usage: $0 "
6 | exit 1
7 | fi
8 |
9 | # PACKAGES=$(npm ls $1 --depth 0 | grep -E '@canvas-js/[a-z-]+' -o)
10 | PACKAGES=$(npm ls $1 --depth 0 | grep -E '@canvas-js/[a-z-]+(@|$| )' | grep -E '@canvas-js/[a-z-]+' -o | sort -u)
11 | PACKAGE_COUNT=$(echo "$PACKAGES" | wc -l | tr -d ' ')
12 | echo "$1 is a dependency of ${PACKAGE_COUNT} packages:"
13 | echo "$PACKAGES"
14 |
15 | # Format the npm install command
16 | WORKSPACE_FLAGS=$(echo "$PACKAGES" | sed 's/^/-w /' | tr '\n' ' ')
17 | INSTALL_COMMAND="npm install $1@latest $WORKSPACE_FLAGS"
18 |
19 | # Echo the command and ask for confirmation
20 | echo "Prepared upgrade command:"
21 | echo ""
22 | echo "$INSTALL_COMMAND"
23 | echo ""
24 | echo "Press Enter to execute this command, or Ctrl+C to cancel..."
25 | read
26 |
27 | # Execute the command
28 | eval "$INSTALL_COMMAND"
29 |
--------------------------------------------------------------------------------