├── .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 | -------------------------------------------------------------------------------- /docs/.vitepress/components/CodeGroupOpener.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /docs/.vitepress/components/HomepageBanner.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /docs/.vitepress/components/HomepageFooter.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /docs/.vitepress/components/Iframe.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /docs/.vitepress/components/TextItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /docs/.vitepress/components/TextRow.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 50 | -------------------------------------------------------------------------------- /docs/.vitepress/components/Version.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 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 |
Loading application...
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 |
Loading application...
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 |
Loading application...
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 | Abranch: 0clock: 0 -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------