diff --git a/.env-example b/.env-example index 48fe09727..7c9c333e3 100644 --- a/.env-example +++ b/.env-example @@ -1,4 +1,4 @@ NEXT_PUBLIC_ALGOLIA_APP_ID= NEXT_PUBLIC_ALGOLIA_API_KEY= NEXT_PUBLIC_ALGOLIA_INDEX= -NEXT_PUBLIC_GTM_ID= \ No newline at end of file +NEXT_PUBLIC_GTM_ID= diff --git a/app/global.css b/app/global.css index 2788c3928..384362221 100644 --- a/app/global.css +++ b/app/global.css @@ -2,7 +2,6 @@ @tailwind components; @tailwind utilities; - @layer base { :root { --background: 40 14.3% 95.9%; @@ -74,7 +73,6 @@ .dark .bitcoin { --primary: 0 0% 100%; --icon: 0 0% 100%; - } } @@ -126,22 +124,42 @@ a[aria-label="Hiro Platform"] { padding: 0; } - a[aria-label="Hiro Platform"] div:hover { background-color: hsla(var(--muted-foreground) / 0.1); } -h1, h2, h3, h4, h5, h6, code, button, .step { +h1, +h2, +h3, +h4, +h5, +h6, +code, +button, +.step { font-family: var(--font-aeonikFono), sans-serif; } -a, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a, option { +a, +h1 a, +h2 a, +h3 a, +h4 a, +h5 a, +h6 a, +option { font-family: var(--font-aeonikFono), sans-serif; } /* TODO: div.prose is for targeting the title, need to fix this approach */ /* TODO: div.prose .flex-1 span is for targeting the text inside the component */ -a, p, li, table, input, div.prose .w-0, div.prose .flex-1 span { +a, +p, +li, +table, +input, +div.prose .w-0, +div.prose .flex-1 span { font-family: var(--font-inter), sans-serif; } @@ -179,7 +197,14 @@ p { line-height: 1.75; } -body > div.guides > div > article > div.prose > figure:nth-child(37) > div > div { +body + > div.guides + > div + > article + > div.prose + > figure:nth-child(37) + > div + > div { background: hsl(var(--code)); } @@ -244,21 +269,32 @@ nav a[href="/guides"] { text-decoration: underline; } -[data-radix-scroll-area-viewport] .flex.flex-col.gap-8.pb-10.pt-4.max-md\:px-4.md\:pr-3.md\:pt-10 { - gap: 0; +[data-radix-scroll-area-viewport] + .flex.flex-col.gap-8.pb-10.pt-4.max-md\:px-4.md\:pr-3.md\:pt-10 { + gap: 0; } /* Target the header toggle */ -body > div > header > nav > div.rounded-md.border.bg-background.p-1.text-sm.text-muted-foreground.max-md\:absolute.max-md\:left-\[50\%\].max-md\:translate-x-\[-50\%\] > a.rounded-md.px-2.py-1.transition-colors.hover\:text-accent-foreground.bg-background.text-accent-foreground { +body + > div + > header + > nav + > div.rounded-md.border.bg-background.p-1.text-sm.text-muted-foreground.max-md\:absolute.max-md\:left-\[50\%\].max-md\:translate-x-\[-50\%\] + > a.rounded-md.px-2.py-1.transition-colors.hover\:text-accent-foreground.bg-background.text-accent-foreground { background-color: hsl(var(--inverted)); color: hsl(var(--background)); } /* Target the search component */ -body > div > header > nav > div.flex.flex-1.flex-row.items-center.justify-end.md\:gap-2 > button.inline-flex.w-full.max-w-\[240px\].items-center.gap-2.rounded-full.border.bg-secondary\/50.p-1\.5.text-sm.text-muted-foreground.transition-colors.hover\:bg-accent.hover\:text-accent-foreground.max-md\:hidden { - background-color: hsl(var(--background)) +body + > div + > header + > nav + > div.flex.flex-1.flex-row.items-center.justify-end.md\:gap-2 + > button.inline-flex.w-full.max-w-\[240px\].items-center.gap-2.rounded-full.border.bg-secondary\/50.p-1\.5.text-sm.text-muted-foreground.transition-colors.hover\:bg-accent.hover\:text-accent-foreground.max-md\:hidden { + background-color: hsl(var(--background)); } p.first\:mt-0 { @@ -280,7 +316,9 @@ p.first\:mt-0:before { margin: 0; } -div.not-prose, div.prose-no-margin, div[role="tablist"] { +div.not-prose, +div.prose-no-margin, +div[role="tablist"] { background: hsl(var(--background)); } @@ -295,7 +333,8 @@ form.not-prose.flex.flex-col.gap-4.rounded-lg.border.bg-card.p-4 { } /* overrides the background of APIInfo and adjusts sticky header on scroll */ -div.prose-no-margin .sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.rounded-lg.border.bg-card.p-3.md\:top-10 { +div.prose-no-margin + .sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.rounded-lg.border.bg-card.p-3.md\:top-10 { background: hsl(var(--background)); top: 4.5rem; } @@ -310,21 +349,24 @@ div.prose-no-margin .sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.roun } /* Style for the POST span */ -.sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.rounded-lg.border.bg-card.p-3.md\:top-10 > span:first-child { +.sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.rounded-lg.border.bg-card.p-3.md\:top-10 + > span:first-child { position: absolute; top: 0.75rem; left: 0.75rem; } /* Style for the button */ -.sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.rounded-lg.border.bg-card.p-3.md\:top-10 > button { +.sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.rounded-lg.border.bg-card.p-3.md\:top-10 + > button { position: absolute; top: 0.75rem; right: 0.75rem; } /* Target the inner div containing the endpoint spans */ -.sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.rounded-lg.border.bg-card.p-3.md\:top-10 > div { +.sticky.top-24.z-\[2\].flex.flex-row.items-center.gap-2.rounded-lg.border.bg-card.p-3.md\:top-10 + > div { display: flex; flex-wrap: wrap; width: 100%; @@ -340,7 +382,10 @@ div.prose div.footer.not-prose { font-family: var(--font-aeonikFono), sans-serif !important; } -.steps > div:nth-child(2) > figure:nth-child(3) > div.flex.flex-row.items-center.gap-2.border-b.bg-muted.px-4.py-1\.5 { +.steps + > div:nth-child(2) + > figure:nth-child(3) + > div.flex.flex-row.items-center.gap-2.border-b.bg-muted.px-4.py-1\.5 { background: hsl(var(--background)) !important; } @@ -355,7 +400,7 @@ div.api-example div.max-xl\:hidden > div:nth-child(1) { background-color: hsl(var(--inverted)); } -.prose :where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)) { +.prose :where(a):not(:where([class~="not-prose"], [class~="not-prose"] *)) { text-decoration-color: var(--secondary) !important; } @@ -373,8 +418,8 @@ div.api-example div.max-xl\:hidden > div:nth-child(1) { } .size-10 { - width: 2.5rem/* 20px */; - height: 2.5rem/* 20px */; + width: 2.5rem /* 20px */; + height: 2.5rem /* 20px */; } /* Add a deprecated strike through to sidebar links */ @@ -394,4 +439,4 @@ a[href="/stacks/api/fees/fee-rate"] { div.divide-y.divide-border.overflow-hidden.rounded-lg.border.bg-card { background: hsl(var(--background)); -} \ No newline at end of file +} diff --git a/components/card.tsx b/components/card.tsx index 1112349b5..cb8d423b6 100644 --- a/components/card.tsx +++ b/components/card.tsx @@ -21,6 +21,7 @@ export type CardProps = { icon?: ReactNode; title: string; description: string; + innerClassName?: string; } & Omit; export function Card({ @@ -38,7 +39,12 @@ export function Card({ props.className )} > -
+
{icon ? (
{icon} diff --git a/content/docs/stacks/stacks.js/concepts/accounts-and-addresses.mdx b/content/docs/stacks/stacks.js/concepts/accounts-and-addresses.mdx index f60a7c4a4..fb13adb68 100644 --- a/content/docs/stacks/stacks.js/concepts/accounts-and-addresses.mdx +++ b/content/docs/stacks/stacks.js/concepts/accounts-and-addresses.mdx @@ -1,5 +1,5 @@ --- -title: Accounts and addresses +title: Accounts & Addresses description: Learn how to get an address from an account. --- @@ -11,46 +11,52 @@ Mainnet: `SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159` Testnet: `ST2F4BK4GZH6YFBNHYDDGN4T1RKBA7DA1BJZPJEJJ` -## Get an address - - - - ```ts - // [!code word:user.profile.stxAddress.mainnet] - import { showConnect } from '@stacks/connect'; - - showConnect({ - appDetails, - userSession, - onFinish: () => { - const user = userSession.loadUserData(); - const address = user.profile.stxAddress.mainnet; - - console.log(address); // SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF - }, - }); - ``` - - - ```ts - // [!code word:getStxAddress] - import { TransactionVersion } from "@stacks/transactions"; - import { generateSecretKey, generateWallet, getStxAddress } from "@stacks/wallet-sdk"; - - const mnemonic = generateSecretKey(); - - const wallet = await generateWallet({ - secretKey: mnemonic, - password: 'optional-password', - }); - - const account = wallet.accounts[0]; - const mainnetAddress = getStxAddress({ - account, - transactionVersion: TransactionVersion.Mainnet - }); - - console.log(mainnetAddress); // SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF - ``` - - +## Getting an address + +### Using Stacks Connect + +```ts +// [!code word:user.profile.stxAddress.mainnet] +import { showConnect } from '@stacks/connect'; + +showConnect({ + appDetails, + userSession, + onFinish: () => { + const user = userSession.loadUserData(); + const address = user.profile.stxAddress.mainnet; + // 'SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF' + }, +}); +``` + +### Using a seed phrase / mnemonic / private key + +```ts +// [!code word:privateKeyToAddress] +import { randomSeedPhrase, generateWallet, privateKeyToAddress } from "@stacks/wallet-sdk"; + +const seed = randomSeedPhrase(); + +const wallet = await generateWallet({ + secretKey: seed, + password: 'secret', +}); + +const address = privateKeyToAddress(wallet.accounts[0].stxPrivateKey, 'mainnet'); +// 'SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF' +``` + +### Using a public key + +```ts +// [!code word:publicKeyToAddress] +import { publicKeyToAddress } from '@stacks/transactions'; + +const address = publicKeyToAddress(publicKey, 'mainnet'); +// 'SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF' +``` + +{/* todo: add accounts code sections, once we have better abstractions */} + +{/* todo: multisig */} diff --git a/content/docs/stacks/stacks.js/concepts/networks.mdx b/content/docs/stacks/stacks.js/concepts/networks.mdx index 000704895..0e58c73f0 100644 --- a/content/docs/stacks/stacks.js/concepts/networks.mdx +++ b/content/docs/stacks/stacks.js/concepts/networks.mdx @@ -1,33 +1,48 @@ --- title: Networks -description: Learn how to create a network instance. +description: Learn how to use different networks. --- -Typically, we speak of `mainnet` and `testnet` as the networks of Stacks. Most wallets will be configured to `mainnet` by default—this is the production environment, the actual blockchain that holds real STX tokens. +import { Star } from 'lucide-react'; -As the name suggests, `testnet` is a public network for testing. It's a separate blockchain state that holds test tokens, which have no value. +Typically, we speak of `mainnet` and `testnet` as the networks of Stacks. +Most wallets are configured to `mainnet` by default—this is the production environment, the actual blockchain that holds real STX tokens. -Developers are encouraged to use `StacksTestnet` for testing before rolling out applications and contracts to `StacksMainnet`. -There is even `StacksDevnet` and `StacksMocknet` for working in a local development environment for development. -Stacks.js functions can be configured to use whichever network you want. +As the name suggests, `testnet` is a public network for testing. +It's a separate blockchain state that holds test tokens, which have no value. -You can learn more about devnet [here](/stacks/clarinet/guides/run-a-local-devnet). +For completeness we also mention `devnet`. +This isn't "one" network, but how developers refer to ephemeral local networks used for testing. +It is the same as `testnet`, but for local development. +[Learn more](/stacks/clarinet/guides/run-a-local-devnet). -## Create a network instance +## Setting the network -```ts twoslash -import { StacksMainnet, StacksTestnet } from '@stacks/network'; +Most Stacks.js functions accept a `network` parameter or an optional last argument. -const mainnet = new StacksMainnet(); -const testnet = new StacksTestnet(); -``` - -The constructors can also be passed a custom URL to an API, if you want to use a different API than the default. +The `network` type is a string, and can be one of: +- `'mainnet'` (default) +- `'testnet'` +- `'devnet'` +- `'mocknet'` (alias of `devnet`) -```ts twoslash -import { StacksMainnet } from '@stacks/network'; +### Examples -const network = new StacksMainnet({ - url: 'https://www.mystacksnode.com/', +Network in transaction signing: +```ts +const tx = makeSTXTokenTransfer({ + // ... + network: 'testnet', }); ``` + +Network in address derivation: +```ts +const address = privateKeyToAddress(privateKey, 'devnet'); +// ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP +``` + + + For more advanced uses, you can pass an object of a network configuration. + Read more about the network object in the [`@stacks/network`](/stacks/stacks.js/packages/network) package. + diff --git a/content/docs/stacks/stacks.js/concepts/private-keys.mdx b/content/docs/stacks/stacks.js/concepts/private-keys.mdx index 8ccbc2537..b79245dde 100644 --- a/content/docs/stacks/stacks.js/concepts/private-keys.mdx +++ b/content/docs/stacks/stacks.js/concepts/private-keys.mdx @@ -1,28 +1,94 @@ --- -title: Private keys -description: Learn how to manage private keys with Stacks.js. +title: Private Keys & Wallets +description: Learn how to manage secrets with Stacks.js. --- -There are two main ways developers build applications on the Stacks blockchain: +import { Card, SmallCard } from '@/components/card'; +import { Play, FileSignature } from 'lucide-react'; -## Without direct private key access - -For example, a web app that allows users to interact with the Stacks blockchain using their Stacks wallet (whether a browser extension or mobile/desktop app). +## WITHOUT direct private key access Most users interact with apps via their favorite Stacks wallet. -Keeping that flow in mind, developers can build web apps that prompt the user for an action (e.g. sign a transaction), and then the wallet will handle the rest. +Developers can build web apps that prompt the user for an action (e.g. sign a transaction), and then the wallet will handle the rest. + +} + href="/stacks/connect" + title="Build Web Apps" + description="You can build Stacks enabled web apps without direct private key access using Stacks Connect." +/> + +## WITH private key access + +Developers can build scripts, backends, and tools intended for full control over private keys. + +- Using the Stacks.js CLI directly to send transactions, or perform common tasks +- Building custom tools using Stacks.js libraries that manage private keys directly + +### Generating random private keys + +Let's start by generating a random private key. +Note that this will return a different value each time you run the code. + +```ts +import { randomPrivateKey } from '@stacks/transactions'; + +const privateKey = randomPrivateKey(); +// 'f5a31c1268a1e37d4edaa05c7d11183c5fbfdcdc48aae36ea4d8cd5cb709932801' +``` + +Private keys are typically represented as hex strings in Stacks.js. +For more control you can use the `PrivateKey` type, which also accepts raw bytes as `Uint8Array` in JavaScript. + +### Using a wallet / seed phrase + +Typically, we don't want to generate random private keys, but instead use a deterministic wallet based on a seed phrase. + +#### Generate a random seed phrase (24 words): + +```ts +import { randomSeedPhrase } from '@stacks/wallet-sdk'; + +const phrase = randomSeedPhrase(); +// "warrior volume sport ... figure cake since" +``` + +#### Generate a wallet from a seed phrase: + +```ts +import { generateWallet } from '@stacks/wallet-sdk'; + +let wallet = generateWallet({ + secretKey: seed, + password: "secret", +}); + +console.log(wallet.accounts[0]); // one account is generated by default +// {  +// stxPrivateKey: '893fc4936c5350394bbe0053d2c31a4b5a44680f6dceb4be2aacaaa3c12e45ff01', +// dataPrivateKey: '676dc36d89ba04cf1789552fc35f3a6279b4b5f13f3d49fb469b0afecea9698f', +// appsKey: 'xprvA19evFHUzrZF3wULUSv1UVcQNRP7xJ2vn2MyAKaUHbT8SvjrrkkhANRG2bewMxHAeDSoUVUBRPiztDc8WwGtz9Ero2GXW5rk3vHHXmutb4V', +// salt: 'cf8a5c7142d842bb38f30c5ab626f7996dd7494236edf21ba00349bb09b9558d', +// index: 0 +// }  +``` -The wallet will act in the security, and best interest of the user, and the user will be able to review the transaction before signing. -[Read more](/stacks.js/connect). +#### Generate more accounts: -## With private key access +```ts +import { generateNewAccount } from '@stacks/wallet-sdk'; -For example, managing funds with the Stacks.js CLI or building a backend (which can sign transactions directly). +wallet = generateNewAccount(wallet); +console.log(wallet.accounts.length); // 2 +``` -While building without direct private key access is most common, there are use cases where devs need direct private key access. +### What else can we do with private keys? -In those instances, developers can build simple scripts and tools intended for "offline" use. -Users may use the Stacks.js CLI directly to send a transaction, and backends may need to automate signing without direct user interaction. +} + href="/stacks/stacks.js/guides/broadcast-transactions" + title="Sign Transactions" + description="Learn how to sign transactions with Stacks.js." +/> -In these cases, developers can use the same libraries used by Stacks wallets for account handling and transaction signing. -[Read more](/stacks.js/learn-the-basics). +{/* todo: add more links and guides: 1. encrypt/decrypt data, 2. create/verify signatures */} diff --git a/content/docs/stacks/stacks.js/concepts/transactions.mdx b/content/docs/stacks/stacks.js/concepts/transactions.mdx index dc59c5d22..08bf1d3f9 100644 --- a/content/docs/stacks/stacks.js/concepts/transactions.mdx +++ b/content/docs/stacks/stacks.js/concepts/transactions.mdx @@ -5,40 +5,49 @@ description: Learn how to create and broadcast transactions with Stacks.js. The following shows how to create a simple transaction (STX transfer) using Stacks.js in different environments. -## Create a transaction - - - - ```js - import { openSTXTransfer } from '@stacks/connect'; - import { StacksTestnet } from '@stacks/network'; - import { AnchorMode } from '@stacks/transactions'; - - openSTXTransfer({ - network: new StacksTestnet(), - - recipient: 'ST39MJ145BR6S8C315AG2BD61SJ16E208P1FDK3AK', // which address you are sending to - amount: 10000, // tokens, denominated in micro-STX - anchorMode: AnchorMode.Any, - - onFinish: response => console.log(response.txid), - onCancel: () => console.log('User canceled'), - }); - ``` - - - For full manual transaction signing, you need to provide the sender's private key. - Treat the private key as a secret and *never* expose it to the public. - - ```js - import { makeSTXTokenTransfer } from '@stacks/transactions'; - - const tx = await makeSTXTokenTransfer({ - recipient: 'ST39MJ145BR6S8C315AG2BD61SJ16E208P1FDK3AK', // which address you are sending to - amount: 10000, // tokens, denominated in micro-STX - anchorMode: 'any', - senderKey: 'c3a2d3...0b1c2', // private key (typically derived from a mnemonic) - }); - ``` - - +## Creating a transaction + +### Using Stacks Connect + +```ts +import { openSTXTransfer } from '@stacks/connect'; +import { AnchorMode } from '@stacks/transactions'; + +openSTXTransfer({ + network: 'testnet', + + recipient: 'ST39MJ145BR6S8C315AG2BD61SJ16E208P1FDK3AK', // which address you are sending to + amount: 10000, // tokens, denominated in micro-STX + anchorMode: AnchorMode.Any, + + onFinish: response => console.log(response.txid), + onCancel: () => console.log('User canceled'), +}); +``` + +### Using a private key + +For full manual transaction signing, you need to provide the sender's private key. +Treat the private key as a secret and *never* expose it to the public. + +```ts +import { makeSTXTokenTransfer } from '@stacks/transactions'; + +const privateKey = randomPrivateKey(); // see "Private Keys & Wallets" page + +const tx = await makeSTXTokenTransfer({ + recipient: 'ST39MJ145BR6S8C315AG2BD61SJ16E208P1FDK3AK', // which address you are sending to + amount: 10000, // tokens, denominated in micro-STX + anchorMode: 'any', + senderKey: privateKey, +}); +``` + +## Different transaction types + +In Stacks.js, we can create transactions for different purposes: +- STX token transfers +- Smart contract calls +- Smart contract deployments + +{/* continue */} diff --git a/content/docs/stacks/stacks.js/index.mdx b/content/docs/stacks/stacks.js/index.mdx index e22bb2c19..e442c2775 100644 --- a/content/docs/stacks/stacks.js/index.mdx +++ b/content/docs/stacks/stacks.js/index.mdx @@ -4,7 +4,8 @@ description: A collection of JavaScript libraries to build web applications on S toc: false --- -import { SecondaryCard } from '@/components/card'; +import { Globe } from 'lucide-react'; +import { Card, SecondaryCard } from '@/components/card'; Stacks.js is a JavaScript/TypeScript SDK for building on the Stacks blockchain. It's a collection of various JavaScript libraries that allow you to broadcast a transaction with the Stacks blockchain, construct post-conditions, and more. Some of the most important building blocks, all in one place. @@ -12,6 +13,15 @@ Stacks.js is separated into many smaller packages, which can be installed indivi For more details, check out out our [installation guide](./stacks.js/installation). +} + href="/stacks/connect" + title="Build a Stacks web app" + description="Integrate Stacks.js into your web apps with Stacks Connect." + className="relative bg-gradient-to-r from-[hsl(var(--border)/1)] via-sky-500/30 to-sky-500/50 border-none hover:bg-gradient-to-r hover:from-border hover:to-[#59564F] dark:hover:to-[#c0bdb4]" + innerClassName='bg-gradient-to-tr from-transparent to-sky-500/20' +/> + ## Guides @@ -38,4 +48,4 @@ For more details, check out out our [installation guide](./stacks.js/installatio Reach out to us on the #stacks-js channel on [Discord](https://stacks.chat/) under the Hiro Developer Tools section. There's also a [weekly office hours](https://events.stacks.co/event/HD16484710) on Discord every Thursday at 11am ET. - \ No newline at end of file + diff --git a/content/docs/stacks/stacks.js/installation.mdx b/content/docs/stacks/stacks.js/installation.mdx index 2300b72c4..b2a546bcf 100644 --- a/content/docs/stacks/stacks.js/installation.mdx +++ b/content/docs/stacks/stacks.js/installation.mdx @@ -28,61 +28,63 @@ The most commonly used packages are: + Build Stacks-ready web apps and connect to user wallets. + ```package-install @stacks/connect ``` - - Build Stacks-ready web apps and connect to user wallets. + Network library for working with Stacks network objects. + ```package-install @stacks/network ``` - - Network and API library for working with Stacks blockchain nodes. + Construct and decode transactions, and work with Clarity smart contracts on the Stacks blockchain. + ```package-install @stacks/transactions ``` - - Construct and decode transactions, and work with Clarity smart contracts on the Stacks blockchain. + API client library for all Stacks Blockchain API endpoints. + ```package-install @stacks/blockchain-api-client ``` - - API client library for all Stacks Blockchain API endpoints. +--- + - [`@stacks/connect`](/stacks/connect/packages/connect) - Build Stacks-ready web applications and connect to user wallets. + [`@stacks/connect`](/stacks/connect/packages/connect) — Build Stacks-ready web applications and connect to user wallets. - `@stacks/auth` - Construct and decode authentication requests for Stacks apps. + `@stacks/auth` — Construct and decode authentication requests for Stacks apps. - [`@stacks/transactions`](/stacks/stacks.js/packages/transactions) - Construct and decode transactions, and work with Clarity smart contracts on the Stacks blockchain. + [`@stacks/transactions`](/stacks/stacks.js/packages/transactions) — Construct and decode transactions, and work with Clarity smart contracts on the Stacks blockchain. - `@stacks/wallet-sdk` - A library for building wallets, managing accounts, and handling keys for the Stacks blockchain. + `@stacks/wallet-sdk` — A library for building wallets, managing accounts, and handling keys for the Stacks blockchain. - `@stacks/storage` - Store and fetch files with Gaia, the decentralized storage system. + `@stacks/storage` — Store and fetch files with Gaia, the decentralized storage system. - `@stacks/profile` - Functions for manipulating user profiles. + `@stacks/profile` — Functions for manipulating user profiles. - `@stacks/encryption` - Encryption functions used by Stacks.js packages. + `@stacks/encryption` — Encryption functions used by Stacks.js packages. - [`@stacks/network`](/stacks/stacks.js/packages/network) - A network and API library for working with Stacks blockchain nodes. + [`@stacks/network`](/stacks/stacks.js/packages/network) — A network and API library for working with Stacks blockchain nodes. - `@stacks/common` - Common utilities used by Stacks.js packages. + `@stacks/common` — Common utilities used by Stacks.js packages. - `@stacks/bns` - A library for interacting with the BNS contract. + `@stacks/bns` — A library for interacting with the BNS contract. - `@stacks/stacking` - A library for PoX stacking. + `@stacks/stacking` — A library for PoX stacking. - `@stacks/cli` - A command line interface to interact with auth, storage, and Stacks transactions. + `@stacks/cli` — A command line interface to interact with auth, storage, and Stacks transactions. - `@stacks/blockchain-api-client` - API client library for all Stacks Blockchain API endpoints. + `@stacks/blockchain-api-client` — API client library for all Stacks Blockchain API endpoints. diff --git a/content/docs/stacks/stacks.js/meta.json b/content/docs/stacks/stacks.js/meta.json index e671a23d0..f8a0ea015 100644 --- a/content/docs/stacks/stacks.js/meta.json +++ b/content/docs/stacks/stacks.js/meta.json @@ -18,7 +18,9 @@ "guides/broadcast-transactions", "guides/post-conditions", "guides/use-with-react-native", - "---Reference---", - "...packages" + "---Reference (latest)---", + "...packages", + "---Reference (6.x.x)---", + "...v6" ] } diff --git a/content/docs/stacks/stacks.js/packages/common.mdx b/content/docs/stacks/stacks.js/packages/common.mdx new file mode 100644 index 000000000..e9a5551e3 --- /dev/null +++ b/content/docs/stacks/stacks.js/packages/common.mdx @@ -0,0 +1,108 @@ +--- +title: '@stacks/common' +description: Common utilities for working with Stacks. +toc: false +--- + +import { Root, API, APIExample } from '@/components/layout'; +import { Property } from 'fumadocs-ui/components/api' +import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; + +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Badge } from '@/components/ui/badge'; + +import { InlineCode } from '@/components/inline-code'; +import { Callout } from "@/components/callout"; + + +This reference refers to the `7.x.x` release of Stacks.js—it's the recommended version to use, but not needed for the Stacks Nakamoto release. +Read the [migration guide](https://github.com/hirosystems/stacks.js/blob/next/.github/MIGRATION.md#stacksjs-5xx--7xx) to learn how to update to the latest version. + + +The `@stacks/common` package contains common utilities for working with Stacks. +This includes fetch helpers, middleware, and various other functions. + +## Installation + +```package-install +@stacks/common@latest +``` + + +## Use the built-in API key middleware + +Some Stacks APIs make use API keys to provide less rate-limited plans. + +```typescript +import { createApiKeyMiddleware, createFetchFn, StacksMainnet } from '@stacks/network'; +import { broadcastTransaction, getNonce, makeSTXTokenTransfer } from '@stacks/transactions'; + +const myApiMiddleware = createApiKeyMiddleware('example_e8e044a3_41d8b0fe_3dd3988ef302'); +const myFetchFn = createFetchFn(myApiMiddleware); // middlewares can be used to create a new fetch function + +const txOptions = { + recipient: 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', + amount: 12345n, + senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', + memo: 'some memo', + anchorMode: AnchorMode.Any, + client: { + fetch: myFetchFn, + } +}; +const transaction = await makeSTXTokenTransfer(txOptions); // fee-estimation will use the custom fetchFn + +const response = await broadcastTransaction(transaction, myMainnet); // make sure to broadcast via the custom network object + +// stacks.js functions, which take a StacksNetwork object will use the custom fetchFn +const nonce = await getNonce('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', myMainnet); +``` + +## Use custom middleware + +Middleware can be used to hook into network calls before sending a request or after receiving a response. + +```typescript +import { createFetchFn, RequestContext, ResponseContext } from '@stacks/common'; + +const preMiddleware = (ctx: RequestContext) => { + ctx.init.headers = new Headers(); + ctx.init.headers.set('x-foo', 'bar'); // override headers and set new `x-foo` header +}; +const postMiddleware = (ctx: ResponseContext) => { + console.log(await ctx.response.json()); // log response body as json +}; + +const fetchFn = createFetchFn({ pre: preMiddleware, post: preMiddleware }); // a middleware can contain `pre`, `post`, or both +const network = new StacksTestnet({ fetchFn }); + +// stacks.js functions, which take a StacksNetwork object will use the custom fetchFn +const nonce = await getNonce('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', network); +``` + +## Get various API URLs + +```typescript +const txBroadcastUrl = network.getBroadcastApiUrl(); + +const feeEstimateUrl = network.getTransferFeeEstimateApiUrl(); + +const address = 'SP2BS6HD7TN34V8Z5BNF8Q2AW3K8K2DPV4264CF26'; +const accountInfoUrl = network.getAccountApiUrl(address); + +const contractName = 'hello_world'; +const abiUrl = network.getAbiApiUrl(address, contractName); + +const functionName = 'hello'; +const readOnlyFunctionCallUrl = network.getReadOnlyFunctionCallApiUrl( + address, + contractName, + functionName +); + +const nodeInfoUrl = network.getInfoUrl(); + +const blockTimeUrl = network.getBlockTimeInfoUrl(); + +const poxInfoUrl = network.getPoxInfoUrl(); +``` diff --git a/content/docs/stacks/stacks.js/packages/network.mdx b/content/docs/stacks/stacks.js/packages/network.mdx index 07c70a68a..99458c2dc 100644 --- a/content/docs/stacks/stacks.js/packages/network.mdx +++ b/content/docs/stacks/stacks.js/packages/network.mdx @@ -12,678 +12,61 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Badge } from '@/components/ui/badge'; import { InlineCode } from '@/components/inline-code'; +import { Callout } from "@/components/callout"; -The Stacks authentication process enables secure user sign-in for web apps by generating and handling encrypted authentication requests. It involves setting up an app domain, configuring permissions, and creating a UserSession to manage user data. + +This reference refers to the `7.x.x` release of Stacks.js—it's the recommended version to use, but not needed for the Stacks Nakamoto release. +Read the [migration guide](https://github.com/hirosystems/stacks.js/blob/next/.github/MIGRATION.md#stacksjs-5xx--7xx) to learn how to update to the latest version. + + +The `@stacks/network` package contains default network configurations for Stacks. ## Installation + +Before you install: most of the time you don't need to use or even install this library directly. +For example, instead of `STACKS_MAINNET`, simply use the string `"mainnet"` as the network parameter. + + ```package-install -@stacks/network +@stacks/network@latest ``` -{/* - - - -
- -

`AppConfig`

- -Configuration data for the current app. - -On browser platforms, creating an instance of this class without any arguments will use `window.location.origin` as the app domain. On non-browser platforms, you need to specify an app domain as the second argument. - -## Parameters - - - -An array of strings representing permissions requested by the app. - -Default: `['store_write']` - - - - - -Stacks apps are uniquely identified by their app domain. - - - - - -Path on app domain to redirect users to after authentication. The authentication response token will be postpended in a query. - - - - - -Path relative to app domain of app's manifest file. - -Default: `"/manifest.json"` - - - - - -The URL of Stacks core node to use for this app. If this is not specified, the default core node will be used. - - - - - -The URL of a web-based authenticator to use in the event the user doesn't have Stacks installed on their machine. If this is not specified, the current default in this library will be used. - - - -
- - - - - - - Setting basic app permissions - - - With advanced scopes - - - - ```tsx twoslash - // @noErrors - // [!code word:AppConfig] - import { AppConfig } from '@stacks/auth'; - - const appDomain = 'https://www.myapp.com'; - const appConfig = new AppConfig( - ['store_write'], - appDomain - ); - ``` - - - - - ```tsx twoslash - import { - UserSession, - makeAuthRequest, - AppConfig - } from '@stacks/auth'; - - const appDomain = 'https://www.myapp.com'; - - const appConfig = new AppConfig(['store_write'], appDomain); - const userSession = new UserSession({ appConfig }); - - // The authentication payloads are encrypted during transit, the encryption key generated below provides this - const transitKey = userSession.generateAndStoreTransitKey(); - - // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. - const redirectUri = 'https://www.myapp.com/auth'; - - // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. - const manifestUri = 'https://www.myapp.com/manifest.json'; - - // Generate the authentication request payload - const authRequest = userSession.makeAuthRequest( - transitKey, - redirectUri, - manifestUri - ); - - ``` - - - - - - ```tsx twoslash - // @noErrors - // [!code word:publish_data] - // [!code word:email] - import { AppConfig } from '@stacks/auth'; - - const appDomain = 'https://www.myapp.com'; - const appConfig = new AppConfig( - ['store_write', 'publish_data', 'email'], - appDomain - ); - ``` - - - - ```tsx twoslash - import { - UserSession, - makeAuthRequest, - AppConfig - } from '@stacks/auth'; - - const appDomain = 'https://www.myapp.com'; - - const appConfig = new AppConfig(['store_write'], appDomain); - const userSession = new UserSession({ appConfig }); - - // The authentication payloads are encrypted during transit, the encryption key generated below provides this - const transitKey = userSession.generateAndStoreTransitKey(); - - // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. - const redirectUri = 'https://www.myapp.com/auth'; - - // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. - const manifestUri = 'https://www.myapp.com/manifest.json'; - - // Generate the authentication request payload - const authRequest = userSession.makeAuthRequest( - transitKey, - redirectUri, - manifestUri - ); - - ``` - - - - - - - - -
- - - -
- -

`UserSession`

- -Represents an instance of a signed in user for a particular app. - -## Functions - - - -Determines if there is an incoming authentication response. - - - - - -Processes the response and provides a `userData` object containing the user's identity, BNS username and profile information. - - - - - -Checks if the user is already authenticated. - - - - - -Retrieves the user's profile data if the user is already authenticated. - - - - - -Encrypts user data for secure storage. - - - - - -Decrypts user data for secure storage. - - - -
- - - - - - Handling pending auth - - - Checking if user is signed in - - - Loading data for signed in user - - - Signing out a user - - - Encrypting user data - - - Decrypting user data - - - - ```tsx twoslash - // @noErrors - // [!code word:isSignInPending] - // [!code word:handlePendingSignIn] - const isPending = userSession.isSignInPending(); - - if (isPending) { - userSession.handlePendingSignIn().then(userData => { - // Do something with userData - }); - } - ``` - - - ```tsx twoslash - // @noErrors - // [!code word:isUserSignedIn] - const isSignedIn = userSession.isUserSignedIn(); - ``` - - - ```tsx twoslash - // @noErrors - // [!code word:loadUserData] - const isSignedIn = userSession.isUserSignedIn(); - - if (isSignedIn) { - // Do something with the signed in user - const userData = userSession.loadUserData(); - } - ``` - - - ```tsx twoslash - // @noErrors - // [!code word:signUserOut] - userSession.signUserOut(); - ``` - - - ```tsx twoslash - // @noErrors - // [!code word:encryptContent] - const message = 'My secret message'; - - const cipherText = await userSession.encryptContent(message); - ``` - - - ```tsx twoslash - // @noErrors - // [!code word:decryptContent] - const message = 'My secret message'; - - const cipherText = await userSession.encryptContent(message); - const plainText = await userSession.decryptContent(cipherText); - ``` - - - - - - - Note that encryption here uses the user's private key associated with your app only. If you need to share this data with another app or other users, you should use the equivalent methods from `@stacks/encryption` and provide a custom private key. - - - - - - -
- - - -
- -

`makeAuthRequest`

- -The Stacks authentication process enables secure user sign-in for web apps by generating and handling encrypted authentication requests. It involves setting up an app domain, configuring permissions, and creating a UserSession to manage user data. - -## Parameters - - - -The authentication payloads are encrypted during transit, the encryption key generated below provides this. - - - - - -A URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. - - - - - -Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. - - - - - -Additional permissions the app is requesting. - - - -
- - - - - - - Generate an authentication request - - - - ```tsx twoslash - // @noErrors - import { - UserSession, - makeAuthRequest, - AppConfig - } from '@stacks/auth'; - - const appDomain = 'https://www.myapp.com'; - - const appConfig = new AppConfig(['store_write'], appDomain); - const userSession = new UserSession({ appConfig }); - - // The authentication payloads are encrypted during transit, the encryption key generated below provides this - const transitKey = userSession.generateAndStoreTransitKey(); - - // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. - const redirectUri = 'https://www.myapp.com/auth'; - - // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. - const manifestUri = 'https://www.myapp.com/manifest.json'; - - // ---cut--- - const authRequest = userSession.makeAuthRequest( - // ^^^^^^^^^^^^^^^ - transitKey, - redirectUri, - manifestUri - ); - ``` - - - - - ```tsx twoslash - import { - UserSession, - makeAuthRequest, - AppConfig - } from '@stacks/auth'; - - const appDomain = 'https://www.myapp.com'; - - const appConfig = new AppConfig(['store_write'], appDomain); - const userSession = new UserSession({ appConfig }); - - // The authentication payloads are encrypted during transit, the encryption key generated below provides this - const transitKey = userSession.generateAndStoreTransitKey(); - - // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. - const redirectUri = 'https://www.myapp.com/auth'; - - // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. - const manifestUri = 'https://www.myapp.com/manifest.json'; - - // Generate the authentication request payload - const authRequest = userSession.makeAuthRequest( - transitKey, - redirectUri, - manifestUri - ); - - ``` - - - - - - - - -
- - - -
- -

`lookupProfile`

- -Look up a user's profile information by their BNS username. - -## Parameters - - - -The BNS username of the user. - - - - - -Defines the network to connect to. Default is 'mainnet'. - - - - - - -URL to fetch the zone file for the username. This is only needed if the zone file does not reside at the default location on the BNS network. - - - -
- - - - - - - Generate an authentication request - - - - ```tsx twoslash - // @noErrors - import { - UserSession, - makeAuthRequest, - AppConfig - } from '@stacks/auth'; - - const appDomain = 'https://www.myapp.com'; - - const appConfig = new AppConfig(['store_write'], appDomain); - const userSession = new UserSession({ appConfig }); - - // The authentication payloads are encrypted during transit, the encryption key generated below provides this - const transitKey = userSession.generateAndStoreTransitKey(); - - // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. - const redirectUri = 'https://www.myapp.com/auth'; - - // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. - const manifestUri = 'https://www.myapp.com/manifest.json'; - - // ---cut--- - const authRequest = userSession.makeAuthRequest( - // ^^^^^^^^^^^^^^^ - transitKey, - redirectUri, - manifestUri - ); - ``` - - - - - ```tsx twoslash - import { - UserSession, - makeAuthRequest, - AppConfig - } from '@stacks/auth'; - - const appDomain = 'https://www.myapp.com'; - - const appConfig = new AppConfig(['store_write'], appDomain); - const userSession = new UserSession({ appConfig }); - - // The authentication payloads are encrypted during transit, the encryption key generated below provides this - const transitKey = userSession.generateAndStoreTransitKey(); - - // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. - const redirectUri = 'https://www.myapp.com/auth'; - - // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. - const manifestUri = 'https://www.myapp.com/manifest.json'; - - // Generate the authentication request payload - const authRequest = userSession.makeAuthRequest( - transitKey, - redirectUri, - manifestUri - ); - - ``` - - - - - - - - -
- -
*/} - - ## Usage -## Create a Stacks mainnet, testnet or mocknet network - -```typescript -import { StacksMainnet, StacksTestnet, StacksMocknet } from '@stacks/network'; +## The network object -const network = new StacksMainnet(); +A network in Stacks.js is an object defining several properties. -const testnet = new StacksTestnet(); +```ts +import { STACKS_MAINNET, STACKS_TESTNET, STACKS_DEVNET } from '@stacks/network'; -const mocknet = new StacksMocknet(); -``` - -## Set a custom node URL - -```typescript -const network = new StacksMainnet({ url: 'https://www.mystacksnode.com/' }); -``` - -## Check if network is mainnet - -```typescript -const isMainnet = network.isMainnet(); +console.log(STACKS_MAINNET); +// { +// chainId: 1, +// transactionVersion: 0, +// peerNetworkId: 385875968, +// magicBytes: 'X2', +// bootAddress: 'SP000000000000000000002Q6VF78', +// addressVersion: { singleSig: 22, multiSig: 20 } +// } ``` ## Network usage in transaction building -```typescript +```ts +// [!code word:network\:] +import { STACKS_MAINNET } from '@stacks/network'; import { makeSTXTokenTransfer } from '@stacks/transactions'; const txOptions = { - network, recipient: 'SP2BS6HD7TN34V8Z5BNF8Q2AW3K8K2DPV4264CF26', - amount: new BigNum(12345), - senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', + amount: 100, + // ... + network: 'mainnet', // 'mainnet', 'testnet', or 'devnet', (defaults to mainnet) + // OR + network: STACKS_MAINNET, // any compatible network object }; const transaction = await makeSTXTokenTransfer(txOptions); ``` - -## Use the built-in API key middleware - -Some Stacks APIs make use API keys to provide less rate-limited plans. - -```typescript -import { createApiKeyMiddleware, createFetchFn, StacksMainnet } from '@stacks/network'; -import { broadcastTransaction, getNonce, makeSTXTokenTransfer } from '@stacks/transactions'; - -const myApiMiddleware = createApiKeyMiddleware('example_e8e044a3_41d8b0fe_3dd3988ef302'); -const myFetchFn = createFetchFn(myApiMiddleware); // middlewares can be used to create a new fetch function -const myMainnet = new StacksMainnet({ fetchFn: myFetchFn }); // the fetchFn options can be passed to a StacksNetwork to override the default fetch function - -const txOptions = { - recipient: 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', - amount: 12345n, - senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', - memo: 'some memo', - anchorMode: AnchorMode.Any, - network: myMainnet, // make sure to pass in the custom network object -}; -const transaction = await makeSTXTokenTransfer(txOptions); // fee-estimation will use the custom fetchFn - -const response = await broadcastTransaction(transaction, myMainnet); // make sure to broadcast via the custom network object - -// stacks.js functions, which take a StacksNetwork object will use the custom fetchFn -const nonce = await getNonce('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', myMainnet); -``` - -## Use custom middleware - -Middleware can be used to hook into network calls before sending a request or after receiving a response. - -```typescript -import { createFetchFn, RequestContext, ResponseContext, StacksTestnet } from '@stacks/network'; -import { broadcastTransaction, getNonce, makeSTXTokenTransfer } from '@stacks/transactions'; - -const preMiddleware = (ctx: RequestContext) => { - ctx.init.headers = new Headers(); - ctx.init.headers.set('x-foo', 'bar'); // override headers and set new `x-foo` header -}; -const postMiddleware = (ctx: ResponseContext) => { - console.log(await ctx.response.json()); // log response body as json -}; - -const fetchFn = createFetchFn({ pre: preMiddleware, post: preMiddleware }); // a middleware can contain `pre`, `post`, or both -const network = new StacksTestnet({ fetchFn }); - -// stacks.js functions, which take a StacksNetwork object will use the custom fetchFn -const nonce = await getNonce('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', network); -``` - -## Get various API URLs - -```typescript -const txBroadcastUrl = network.getBroadcastApiUrl(); - -const feeEstimateUrl = network.getTransferFeeEstimateApiUrl(); - -const address = 'SP2BS6HD7TN34V8Z5BNF8Q2AW3K8K2DPV4264CF26'; -const accountInfoUrl = network.getAccountApiUrl(address); - -const contractName = 'hello_world'; -const abiUrl = network.getAbiApiUrl(address, contractName); - -const functionName = 'hello'; -const readOnlyFunctionCallUrl = network.getReadOnlyFunctionCallApiUrl( - address, - contractName, - functionName -); - -const nodeInfoUrl = network.getInfoUrl(); - -const blockTimeUrl = network.getBlockTimeInfoUrl(); - -const poxInfoUrl = network.getPoxInfoUrl(); -``` diff --git a/content/docs/stacks/stacks.js/roadmap.mdx b/content/docs/stacks/stacks.js/roadmap.mdx index 24cb57246..21e7f9423 100644 --- a/content/docs/stacks/stacks.js/roadmap.mdx +++ b/content/docs/stacks/stacks.js/roadmap.mdx @@ -1,35 +1,328 @@ --- -title: Roadmap +title: Announcing Stacks.js v7 description: Discover the future of Stacks.js. --- -We're working hard on `v7` of Stacks.js. This version will bring a lot of improvements and new features, but it also adds some breaking changes. +Read about what's new in the latest version of Stacks.js. +This version comes with a lot of improvements and new features, but it also adds some breaking changes. -Want to use these latest `next` features? Install the canary version of the packages, e.g. +Want to use these latest features? Install packages using the `latest` tag: ```package-install -@stacks/transactions@next +@stacks/transactions@latest ``` -## Breaking changes +### Strings +_aka, reducing wrapper types and magic numbers_ -- **Getting rid of the network class**
Instead, Stacks networks are now static objects, which are easier to handle. Also, the fetching logic is separated from the network object. -- **Changing internal Clarity representation**
Clarity values in JS are hard to inspect. This is now simpler using easy to construct and debug objects. -- **Changing internal post-conditions representation**
Post-conditions are now easier to inspect as well. -- **Defaulting to hex**
In a lot of places Stacks.js defaults to bytes/Uint8Arrays. This is now updated to default to hex strings, which should be enough for most users. For advanced users, the bytes are still available as new methods. -- **Simplifying addressess & tokens**
Addresses and tokens can be confusing. It's preferred to always have a single string representation for addresses and tokens. -- **Removing Triplesec**
Triplesec has been a legacy mnemonic encryption option for a while. We will remove it to get rid of a dependency on a library that is no longer actively maintained. -- **Remove legacy Blockstack authentication**
The Blockstack authentication is no longer maintained and will be removed from Stacks.js (mainly CLI). +String Meme -### **Deprecations** +#### Problem -- **Remove wallet-sdk "restoring" and "config" features**
These concepts have lead to confusion and have been avoided by wallets. Wallets will not store additional information on Gaia and will not "restore"/detect which accounts have previously been used. +Many Stacks.js representations we're not debuggable for developers. +Logging Clarity values resulted in unintelligible `type` and `value` numbers, confusing even experienced Stacks developers. +Private keys were also included unnecessary `type` properties with raw byte arrays. +"Magic" numbers are all accross the codebase (as they are needed for serialization), but these shouldn't be part of the "public" interfaces. -## New Features +#### Solution -- **Adding a maintained `StacksApiClient`**
So far, we only had auto-generated wrappers for the API. Now we will provide a maintained client that is easier to use with post-processing of information (e.g. Clarity values) to make them easier to use. +We switched to a system where most values will be represented as strings. +This makes them easier to inspect and diff. + +Clarity example, [Read more](#clarity-representation) +```ts +// [!code word:OLD] +// [!code word:NEW] +// OLD: +{ type: 1, value: 12n } + +// NEW: +{ type: "uint", value: "12" } +``` + +Private key example, [Read more](/stacks/stacks.js/concepts/private-keys) +```ts +// [!code word:OLD] +// [!code word:NEW] +signMessageHashRsv({ + // OLD: + privateKey: createStacksPrivateKey("f5a3...2801"), // { compressed: true, data: [245,163,...] } + // NEW: + privateKey: "f5a3...2801" +}) +``` + +This breaks the signatures of many functions: + +- `signMessageHashRsv`, `signWithKey` now return the message signature as a `string` directly. +- `nextSignature`, `nextVerification`, `publicKeyFromSignatureVrs`, `publicKeyFromSignatureRsv` now take in the message signature as a `string`. + +### Stacks Network + +For a long time, Stacks.js "network" instances were used for "networking" and "network" definitions. +This caused confusion, as most users use `mainnet` or `testnet` for most of their interactions. +The "networking" (aka fetching) logic is now more clearly customizable. + +From now on "network" objects are static (aka constants) and don't require instantiation. + +These changes should make it more obvious when functions are using network object properties vs when they are doing actual networking. + +In most cases, developers shouldn't need the `@stacks/network` package anymore. +The `network` parameter can be used with string literals: `'mainnet'`, `'testnet'`, `'devnet'`, `'mocknet'`. + +```ts +// [!code word:OLD] +// [!code word:NEW] +// OLD: +import { StacksTestnet } from '@stacks/network'; + +makeSTXTokenTransfer({ + network: new StacksTestnet(), // [!code highlight] + // ... +}); + +// NEW: +makeSTXTokenTransfer({ + network: 'testnet' // [!code highlight] + // ... +}); +``` + + +#### Stacks Network `client` + +In case a function also takes a `client` parameter, it will be doing actual networking. +This way you can use string literal networks with a custom node. +You can also still use network objects with the `client` parameter as part of the network object. +The `client` parameter can be any object-like structure containing a `baseUrl` and `fetch` property. + +- The `baseUrl` property should be a string containing the base URL of the Stacks node you want to use. +- The `fetch` property can be any (fetch)[https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API] compatible function. + +The following diffs show examples of how to migrate to the new pattern. + +```diff +const transaction = await makeSTXTokenTransfer({ + // ... +- network: new StacksTestnet({ url: "mynode-optional.com", fetchFn: myFetch }), // optional options ++ network: 'testnet', // optional, defaults to 'mainnet' ++ client: { baseUrl: "mynode-optional.com", fetch: myFetchOptional } // optional, defaults inferred from network +}); +``` + +The `client` property is also part of the network object. +You can still keep ONE single network object for your whole application. + +```ts +// [!code word:OLD] +// [!code word:NEW] +// OLD: +import { StacksTestnet } from '@stacks/network'; + +const network = new StacksTestnet({ url: "https://mynode.com", fetchFn: myFetch }); + +// NEW: +import { STACKS_TESTNET } from '@stacks/network'; + +const network = { + ...STACKS_TESTNET, // extending a static object + client: { baseUrl: "https://mynode.com", fetch: myFetch } +}; +``` + +### A To B Helpers + +Where possible, Stacks.js now offers function to translate between different representations and concepts. +The naming is consistent across the board and uses `A To B` naming. +For example, if we have a private key and want to get the address, we can use the `privateKeyToAddress` function. + +```ts +// [!code word:privateKeyToAddress] +import { privateKeyToAddress } from "@stacks/transactions"; + +const privateKey = "f5a3...2801"; +const address = privateKeyToAddress(privateKey); // SP1MXSZF4NFC8JQ1TTYGEC2WADMC7Y3GHVZYRX6RF +``` + +### Fetch Methods + +To make it easier to discover all fetching functions, they now all start with `fetch`. + +The following methods were renamed: + +- `estimateFee` → `fetchFeeEstimate` +- `estimateTransfer` → `fetchFeeEstimateTransfer` +- `estimateTransaction` → `fetchFeeEstimateTransaction` +- `getAbi` → `fetchAbi` +- `getNonce` → `fetchNonce` +- `getContractMapEntry` → `fetchContractMapEntry` +- `callReadOnlyFunction` → `fetchCallReadOnlyFunction` + +`broadcastTransaction` wasn't renamed to highlight the uniqueness of the method. +Namely, the node/API it is sent to will "broadcast" the transaction to the mempool and is more of an irreversible action. + +### Clarity Representation + +The `ClarityType` enum was replaced by a more readable version. +The previous (wire format compatible) enum is still available as `ClarityWireType`. +These types are considered somewhat internal and shouldn't cause breaking changes for most use-cases. + +The property holding the value of the data type is now called `value` in all cases. +Previously, there was a mix of `value`, `list`, `buffer` etc. +For `bigint` values, the type of the `value` property is a now `string`, for better serialization compatibility. + +```diff +{ +- type: 1, ++ type: "uint", +- value: 12n, ++ value: "12", +} +``` + +```diff +{ +- type: 11, ++ type: "list", +- list: [ ... ], ++ value: [ ... ], +} +``` + +### Post-conditions + +The old `PostCondition` type was renamed to `PostConditionWire`. +A new human-readable `PostCondition` type was introduced in its place. + +Below is an example of the new `PostCondition` types. + +```ts +// STX post-condition +const stxPostCondition: StxPostCondition = { + type: 'stx-postcondition', + address: 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B', + condition: 'gte', + amount: '100', +}; + +// Fungible token post-condition +const ftPostCondition: FungiblePostCondition = { + type: 'ft-postcondition', + address: 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B', + condition: 'eq', + amount: '100', + asset: 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.my-ft-token::my-token', +}; + +// Non-fungible token post-condition +const nftPostCondition: NonFungiblePostCondition = { + type: 'nft-postcondition', + address: 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B', + condition: 'sent', + asset: 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.my-nft::my-asset', + assetId: Cl.uint(602), +}; +``` + +### Advanced + +#### serialize methods + +Existing methods now take or return **hex-encoded strings** _instead_ of `Uint8Array`s. + +> If you were already converting returned bytes to hex-strings in your code, you can now skip the conversion step — hex-strings are the new default. + +For easier migrating, renaming the following methods is possible to keep the previous behavior: + +- `StacksTransaction.serialize` → `StacksTransaction.serializeBytes` +- `serializeCV` → `serializeCVBytes` +- `serializeAddress` → `serializeAddressBytes` +- `deserializeAddress` → `deserializeAddressBytes` +- `serializeLPList` → `serializeLPListBytes` +- `deserializeLPList` → `deserializeLPListBytes` +- `serializeLPString` → `serializeLPStringBytes` +- `deserializeLPString` → `deserializeLPStringBytes` +- `serializePayload` → `serializePayloadBytes` +- `deserializePayload` → `deserializePayloadBytes` +- `serializePublicKey` → `serializePublicKeyBytes` +- `deserializePublicKey` → `deserializePublicKeyBytes` +- `serializeStacksMessage` → `serializeStacksMessageBytes` +- `deserializeStacksMessage` → `deserializeStacksMessageBytes` +- `serializeMemoString` → `serializeMemoStringBytes` +- `deserializeMemoString` → `deserializeMemoStringBytes` +- `serializeTransactionAuthField` → `serializeTransactionAuthFieldBytes` +- `deserializeTransactionAuthField` → `deserializeTransactionAuthFieldBytes` +- `serializeMessageSignature` → `serializeMessageSignatureBytes` +- `deserializeMessageSignature` → `deserializeMessageSignatureBytes` +- `serializePostCondition` → `serializePostConditionBytes` +- `deserializePostCondition` → `deserializePostConditionBytes` +- `serializeStacksMessage` → `serializeStacksWireBytes` +- `deserializeStacksMessage` → `deserializeStacksWireBytes` + +#### Asset Helper Methods + +The following interfaces and methods were renamed: + +- `AssetInfo` → `Asset` +- `StacksWireType.AssetInfo` → `StacksWireType.Asset` +- `createAssetInfo` → `createAsset` +- `parseAssetInfoString` → `parseAssetString` + +#### CLI + +- Removed the `authenticator` method for legacy Blockstack authentication. + +#### Triplesec + +Support for encrypting/decrypting mnemonics with `triplesec` was removed. +This impacts the methods: `decrypt`, `decryptMnemonic`, and `decryptLegacy`. +Make sure to update your code to if mnemonics are stored somewhere encrypted using the legacy method. + +#### WireType + +Renamed internals to avoid confusion between "message" and wire-format for serialization. +This is only used for advanced serialization use-cases internally and should not be needed for most users. + +- `StacksMessage` → `StacksWire` +- `StacksMessageType` → `StacksWireType` +- `serializeStacksMessage` → `serializeStacksWireBytes` +- `deserializeStacksMessage` → `deserializeStacksWireBytes` + +More types were renamed to indicate use for serialization to _wire-format_: + +- `MessageSignature` → `MessageSignatureWire` +- `StacksPublicKey` → `PublicKeyWire` +- `TransactionAuthField` → `TransactionAuthFieldWire` +- `Asset` → `AssetWire` +- `Address` → `AddressWire` +- `PostCondition` → `PostConditionWire` +- `PostConditionPrincipal` → `PostConditionPrincipalWire` +- `STXPostCondition` → `STXPostConditionWire` +- `FungiblePostCondition` → `FungiblePostConditionWire` +- `NonFungiblePostCondition` → `NonFungiblePostConditionWire` +- `LengthPrefixedString` → `LengthPrefixedStringWire` +- `CoinbasePayload` → `CoinbasePayloadWire` +- `PoisonPayload` → `PoisonPayloadWire` +- `SmartContractPayload` → `SmartContractPayloadWire` +- `TokenTransferPayload` → `TokenTransferPayloadWire` +- `VersionedSmartContractPayload` → `VersionedSmartContractPayloadWire` +- `NakamotoCoinbasePayload` → `NakamotoCoinbasePayloadWire` +- `TenureChangePayload` → `TenureChangePayloadWire` +- `StandardPrincipal` → `StandardPrincipalWire` +- `ContractPrincipal` → `ContractPrincipalWire` + +#### Signed BigInt + +The `intToBigInt` method no longer supports two's complement signed integers and removed the `signed` boolean parameter. +This likely was a misunderstood and unused feature. + +#### Refactorings + +- `AddressHashMode`: The `Serialize` prefixes were removed for brevity. +- `makeRandomPrivKey` was renamed to `randomPrivateKey` and now returns a compressed private key. +- `generateSecretKey` was renamed to `randomSeedPhrase`. **Have an idea?** Please let us know on [Discord #stacks-js](https://stacks.chat) or open an issue on [Github](https://github.com/hirosystems/stacks.js/issues/new/choose). diff --git a/content/docs/stacks/stacks.js/v6/meta.json b/content/docs/stacks/stacks.js/v6/meta.json new file mode 100644 index 000000000..8165aaeaa --- /dev/null +++ b/content/docs/stacks/stacks.js/v6/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Packages", + "pages": ["..."], + "defaultOpen": false +} \ No newline at end of file diff --git a/content/docs/stacks/stacks.js/v6/network.mdx b/content/docs/stacks/stacks.js/v6/network.mdx new file mode 100644 index 000000000..10628eeb8 --- /dev/null +++ b/content/docs/stacks/stacks.js/v6/network.mdx @@ -0,0 +1,689 @@ +--- +title: '@stacks/network' +description: Network and API library for working with Stacks blockchain nodes. +toc: false +--- + +import { Root, API, APIExample } from '@/components/layout'; +import { Property } from 'fumadocs-ui/components/api' +import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; + +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Badge } from '@/components/ui/badge'; + +import { InlineCode } from '@/components/inline-code'; + +The Stacks authentication process enables secure user sign-in for web apps by generating and handling encrypted authentication requests. It involves setting up an app domain, configuring permissions, and creating a UserSession to manage user data. + +## Installation + +```package-install +@stacks/network +``` + +{/* + + + +
+ +

`AppConfig`

+ +Configuration data for the current app. + +On browser platforms, creating an instance of this class without any arguments will use `window.location.origin` as the app domain. On non-browser platforms, you need to specify an app domain as the second argument. + +## Parameters + + + +An array of strings representing permissions requested by the app. + +Default: `['store_write']` + + + + + +Stacks apps are uniquely identified by their app domain. + + + + + +Path on app domain to redirect users to after authentication. The authentication response token will be postpended in a query. + + + + + +Path relative to app domain of app's manifest file. + +Default: `"/manifest.json"` + + + + + +The URL of Stacks core node to use for this app. If this is not specified, the default core node will be used. + + + + + +The URL of a web-based authenticator to use in the event the user doesn't have Stacks installed on their machine. If this is not specified, the current default in this library will be used. + + + +
+ + + + + + + Setting basic app permissions + + + With advanced scopes + + + + ```tsx twoslash + // @noErrors + // [!code word:AppConfig] + import { AppConfig } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + const appConfig = new AppConfig( + ['store_write'], + appDomain + ); + ``` + + + + + ```tsx twoslash + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // Generate the authentication request payload + const authRequest = userSession.makeAuthRequest( + transitKey, + redirectUri, + manifestUri + ); + + ``` + + + + + + ```tsx twoslash + // @noErrors + // [!code word:publish_data] + // [!code word:email] + import { AppConfig } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + const appConfig = new AppConfig( + ['store_write', 'publish_data', 'email'], + appDomain + ); + ``` + + + + ```tsx twoslash + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // Generate the authentication request payload + const authRequest = userSession.makeAuthRequest( + transitKey, + redirectUri, + manifestUri + ); + + ``` + + + + + + + + +
+ + + +
+ +

`UserSession`

+ +Represents an instance of a signed in user for a particular app. + +## Functions + + + +Determines if there is an incoming authentication response. + + + + + +Processes the response and provides a `userData` object containing the user's identity, BNS username and profile information. + + + + + +Checks if the user is already authenticated. + + + + + +Retrieves the user's profile data if the user is already authenticated. + + + + + +Encrypts user data for secure storage. + + + + + +Decrypts user data for secure storage. + + + +
+ + + + + + Handling pending auth + + + Checking if user is signed in + + + Loading data for signed in user + + + Signing out a user + + + Encrypting user data + + + Decrypting user data + + + + ```tsx twoslash + // @noErrors + // [!code word:isSignInPending] + // [!code word:handlePendingSignIn] + const isPending = userSession.isSignInPending(); + + if (isPending) { + userSession.handlePendingSignIn().then(userData => { + // Do something with userData + }); + } + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:isUserSignedIn] + const isSignedIn = userSession.isUserSignedIn(); + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:loadUserData] + const isSignedIn = userSession.isUserSignedIn(); + + if (isSignedIn) { + // Do something with the signed in user + const userData = userSession.loadUserData(); + } + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:signUserOut] + userSession.signUserOut(); + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:encryptContent] + const message = 'My secret message'; + + const cipherText = await userSession.encryptContent(message); + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:decryptContent] + const message = 'My secret message'; + + const cipherText = await userSession.encryptContent(message); + const plainText = await userSession.decryptContent(cipherText); + ``` + + + + + + + Note that encryption here uses the user's private key associated with your app only. If you need to share this data with another app or other users, you should use the equivalent methods from `@stacks/encryption` and provide a custom private key. + + + + + + +
+ + + +
+ +

`makeAuthRequest`

+ +The Stacks authentication process enables secure user sign-in for web apps by generating and handling encrypted authentication requests. It involves setting up an app domain, configuring permissions, and creating a UserSession to manage user data. + +## Parameters + + + +The authentication payloads are encrypted during transit, the encryption key generated below provides this. + + + + + +A URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + + + + + +Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + + + + + +Additional permissions the app is requesting. + + + +
+ + + + + + + Generate an authentication request + + + + ```tsx twoslash + // @noErrors + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // ---cut--- + const authRequest = userSession.makeAuthRequest( + // ^^^^^^^^^^^^^^^ + transitKey, + redirectUri, + manifestUri + ); + ``` + + + + + ```tsx twoslash + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // Generate the authentication request payload + const authRequest = userSession.makeAuthRequest( + transitKey, + redirectUri, + manifestUri + ); + + ``` + + + + + + + + +
+ + + +
+ +

`lookupProfile`

+ +Look up a user's profile information by their BNS username. + +## Parameters + + + +The BNS username of the user. + + + + + +Defines the network to connect to. Default is 'mainnet'. + + + + + + +URL to fetch the zone file for the username. This is only needed if the zone file does not reside at the default location on the BNS network. + + + +
+ + + + + + + Generate an authentication request + + + + ```tsx twoslash + // @noErrors + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // ---cut--- + const authRequest = userSession.makeAuthRequest( + // ^^^^^^^^^^^^^^^ + transitKey, + redirectUri, + manifestUri + ); + ``` + + + + + ```tsx twoslash + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // Generate the authentication request payload + const authRequest = userSession.makeAuthRequest( + transitKey, + redirectUri, + manifestUri + ); + + ``` + + + + + + + + +
+ +
*/} + + +## Usage + +## Create a Stacks mainnet, testnet or mocknet network + +```typescript +import { StacksMainnet, StacksTestnet, StacksMocknet } from '@stacks/network'; + +const network = new StacksMainnet(); + +const testnet = new StacksTestnet(); + +const mocknet = new StacksMocknet(); +``` + +## Set a custom node URL + +```typescript +const network = new StacksMainnet({ url: 'https://www.mystacksnode.com/' }); +``` + +## Check if network is mainnet + +```typescript +const isMainnet = network.isMainnet(); +``` + +## Network usage in transaction building + +```typescript +import { makeSTXTokenTransfer } from '@stacks/transactions'; + +const txOptions = { + network, + recipient: 'SP2BS6HD7TN34V8Z5BNF8Q2AW3K8K2DPV4264CF26', + amount: new BigNum(12345), + senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', +}; + +const transaction = await makeSTXTokenTransfer(txOptions); +``` + +## Use the built-in API key middleware + +Some Stacks APIs make use API keys to provide less rate-limited plans. + +```typescript +import { createApiKeyMiddleware, createFetchFn, StacksMainnet } from '@stacks/network'; +import { broadcastTransaction, getNonce, makeSTXTokenTransfer } from '@stacks/transactions'; + +const myApiMiddleware = createApiKeyMiddleware('example_e8e044a3_41d8b0fe_3dd3988ef302'); +const myFetchFn = createFetchFn(myApiMiddleware); // middlewares can be used to create a new fetch function +const myMainnet = new StacksMainnet({ fetchFn: myFetchFn }); // the fetchFn options can be passed to a StacksNetwork to override the default fetch function + +const txOptions = { + recipient: 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', + amount: 12345n, + senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', + memo: 'some memo', + anchorMode: AnchorMode.Any, + network: myMainnet, // make sure to pass in the custom network object +}; +const transaction = await makeSTXTokenTransfer(txOptions); // fee-estimation will use the custom fetchFn + +const response = await broadcastTransaction(transaction, myMainnet); // make sure to broadcast via the custom network object + +// stacks.js functions, which take a StacksNetwork object will use the custom fetchFn +const nonce = await getNonce('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', myMainnet); +``` + +## Use custom middleware + +Middleware can be used to hook into network calls before sending a request or after receiving a response. + +```typescript +import { createFetchFn, RequestContext, ResponseContext, StacksTestnet } from '@stacks/network'; +import { broadcastTransaction, getNonce, makeSTXTokenTransfer } from '@stacks/transactions'; + +const preMiddleware = (ctx: RequestContext) => { + ctx.init.headers = new Headers(); + ctx.init.headers.set('x-foo', 'bar'); // override headers and set new `x-foo` header +}; +const postMiddleware = (ctx: ResponseContext) => { + console.log(await ctx.response.json()); // log response body as json +}; + +const fetchFn = createFetchFn({ pre: preMiddleware, post: preMiddleware }); // a middleware can contain `pre`, `post`, or both +const network = new StacksTestnet({ fetchFn }); + +// stacks.js functions, which take a StacksNetwork object will use the custom fetchFn +const nonce = await getNonce('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', network); +``` + +## Get various API URLs + +```typescript +const txBroadcastUrl = network.getBroadcastApiUrl(); + +const feeEstimateUrl = network.getTransferFeeEstimateApiUrl(); + +const address = 'SP2BS6HD7TN34V8Z5BNF8Q2AW3K8K2DPV4264CF26'; +const accountInfoUrl = network.getAccountApiUrl(address); + +const contractName = 'hello_world'; +const abiUrl = network.getAbiApiUrl(address, contractName); + +const functionName = 'hello'; +const readOnlyFunctionCallUrl = network.getReadOnlyFunctionCallApiUrl( + address, + contractName, + functionName +); + +const nodeInfoUrl = network.getInfoUrl(); + +const blockTimeUrl = network.getBlockTimeInfoUrl(); + +const poxInfoUrl = network.getPoxInfoUrl(); +``` diff --git a/content/docs/stacks/stacks.js/v6/transactions.mdx b/content/docs/stacks/stacks.js/v6/transactions.mdx new file mode 100644 index 000000000..659ba6cc2 --- /dev/null +++ b/content/docs/stacks/stacks.js/v6/transactions.mdx @@ -0,0 +1,1077 @@ +--- +title: '@stacks/transactions' +description: Construct, decode transactions and work with Clarity smart contracts on the Stacks blockchain. +toc: false +--- + +import { Root, API, APIExample } from '@/components/layout'; +import { Property } from 'fumadocs-ui/components/api' +import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; + +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Badge } from '@/components/ui/badge'; + +import { InlineCode } from '@/components/inline-code'; + +The Stacks authentication process enables secure user sign-in for web apps by generating and handling encrypted authentication requests. It involves setting up an app domain, configuring permissions, and creating a UserSession to manage user data. + +## Installation + +```package-install +@stacks/transactions +``` + +{/* + + + +
+ +

`AppConfig`

+ +Configuration data for the current app. + +On browser platforms, creating an instance of this class without any arguments will use `window.location.origin` as the app domain. On non-browser platforms, you need to specify an app domain as the second argument. + +## Parameters + + + +An array of strings representing permissions requested by the app. + +Default: `['store_write']` + + + + + +Stacks apps are uniquely identified by their app domain. + + + + + +Path on app domain to redirect users to after authentication. The authentication response token will be postpended in a query. + + + + + +Path relative to app domain of app's manifest file. + +Default: `"/manifest.json"` + + + + + +The URL of Stacks core node to use for this app. If this is not specified, the default core node will be used. + + + + + +The URL of a web-based authenticator to use in the event the user doesn't have Stacks installed on their machine. If this is not specified, the current default in this library will be used. + + + +
+ + + + + + + Setting basic app permissions + + + With advanced scopes + + + + ```tsx twoslash + // @noErrors + // [!code word:AppConfig] + import { AppConfig } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + const appConfig = new AppConfig( + ['store_write'], + appDomain + ); + ``` + + + + + ```tsx twoslash + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // Generate the authentication request payload + const authRequest = userSession.makeAuthRequest( + transitKey, + redirectUri, + manifestUri + ); + + ``` + + + + + + ```tsx twoslash + // @noErrors + // [!code word:publish_data] + // [!code word:email] + import { AppConfig } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + const appConfig = new AppConfig( + ['store_write', 'publish_data', 'email'], + appDomain + ); + ``` + + + + ```tsx twoslash + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // Generate the authentication request payload + const authRequest = userSession.makeAuthRequest( + transitKey, + redirectUri, + manifestUri + ); + + ``` + + + + + + + + +
+ + + +
+ +

`UserSession`

+ +Represents an instance of a signed in user for a particular app. + +## Functions + + + +Determines if there is an incoming authentication response. + + + + + +Processes the response and provides a `userData` object containing the user's identity, BNS username and profile information. + + + + + +Checks if the user is already authenticated. + + + + + +Retrieves the user's profile data if the user is already authenticated. + + + + + +Encrypts user data for secure storage. + + + + + +Decrypts user data for secure storage. + + + +
+ + + + + + Handling pending auth + + + Checking if user is signed in + + + Loading data for signed in user + + + Signing out a user + + + Encrypting user data + + + Decrypting user data + + + + ```tsx twoslash + // @noErrors + // [!code word:isSignInPending] + // [!code word:handlePendingSignIn] + const isPending = userSession.isSignInPending(); + + if (isPending) { + userSession.handlePendingSignIn().then(userData => { + // Do something with userData + }); + } + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:isUserSignedIn] + const isSignedIn = userSession.isUserSignedIn(); + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:loadUserData] + const isSignedIn = userSession.isUserSignedIn(); + + if (isSignedIn) { + // Do something with the signed in user + const userData = userSession.loadUserData(); + } + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:signUserOut] + userSession.signUserOut(); + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:encryptContent] + const message = 'My secret message'; + + const cipherText = await userSession.encryptContent(message); + ``` + + + ```tsx twoslash + // @noErrors + // [!code word:decryptContent] + const message = 'My secret message'; + + const cipherText = await userSession.encryptContent(message); + const plainText = await userSession.decryptContent(cipherText); + ``` + + + + + + + Note that encryption here uses the user's private key associated with your app only. If you need to share this data with another app or other users, you should use the equivalent methods from `@stacks/encryption` and provide a custom private key. + + + + + + +
+ + + +
+ +

`makeAuthRequest`

+ +The Stacks authentication process enables secure user sign-in for web apps by generating and handling encrypted authentication requests. It involves setting up an app domain, configuring permissions, and creating a UserSession to manage user data. + +## Parameters + + + +The authentication payloads are encrypted during transit, the encryption key generated below provides this + + + + + +A URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + + + + + +Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + + + + + +Additional permissions the app is requesting + + + +
+ + + + + + + Generate an authentication request + + + + ```tsx twoslash + // @noErrors + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // ---cut--- + const authRequest = userSession.makeAuthRequest( + // ^^^^^^^^^^^^^^^ + transitKey, + redirectUri, + manifestUri + ); + ``` + + + + + ```tsx twoslash + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // Generate the authentication request payload + const authRequest = userSession.makeAuthRequest( + transitKey, + redirectUri, + manifestUri + ); + + ``` + + + + + + + + +
+ + + +
+ +

`lookupProfile`

+ +Look up a user's profile information by their BNS username. + +## Parameters + + + +The BNS username of the user. + + + + + +Defines the network to connect to. Default is 'mainnet'. + + + + + + +URL to fetch the zone file for the username. This is only needed if the zone file does not reside at the default location on the BNS network. + + + +
+ + + + + + + Generate an authentication request + + + + ```tsx twoslash + // @noErrors + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // ---cut--- + const authRequest = userSession.makeAuthRequest( + // ^^^^^^^^^^^^^^^ + transitKey, + redirectUri, + manifestUri + ); + ``` + + + + + ```tsx twoslash + import { + UserSession, + makeAuthRequest, + AppConfig + } from '@stacks/auth'; + + const appDomain = 'https://www.myapp.com'; + + const appConfig = new AppConfig(['store_write'], appDomain); + const userSession = new UserSession({ appConfig }); + + // The authentication payloads are encrypted during transit, the encryption key generated below provides this + const transitKey = userSession.generateAndStoreTransitKey(); + + // The Stacks auth process will open a compatible Stacks authenticator or browser extension to perform the authentication. So you will need to provide a redirect URL which the authenticator or extension can redirect to with the authentication payload. This page should process the authentication payload. + const redirectUri = 'https://www.myapp.com/auth'; + + // Set the location of your app manifest file. This file contains information about your app that is shown to the user during authentication. + const manifestUri = 'https://www.myapp.com/manifest.json'; + + // Generate the authentication request payload + const authRequest = userSession.makeAuthRequest( + transitKey, + redirectUri, + manifestUri + ); + + ``` + + + + + + + + +
+ +
*/} + +This library supports the creation of the following Stacks transaction types: + +1. STX token transfer +2. Smart contract deploy +3. Smart contract function call + +## Key Generation + +```typescript +import { createStacksPrivateKey, makeRandomPrivKey, getPublicKey } from '@stacks/transactions'; + +// Random key +const privateKey = makeRandomPrivKey(); +// Get public key from private +const publicKey = getPublicKey(privateKey); + +// Private key from hex string +const key = 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01'; +const privateKey = createStacksPrivateKey(key); +``` + +## STX Token Transfer Transaction + +```typescript +import { makeSTXTokenTransfer, broadcastTransaction, AnchorMode } from '@stacks/transactions'; + +const txOptions = { + recipient: 'SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159', + amount: 12345n, + senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', + network: 'testnet', // for mainnet, use 'mainnet' + memo: 'test memo', + nonce: 0n, // set a nonce manually if you don't want builder to fetch from a Stacks node + fee: 200n, // set a tx fee if you don't want the builder to estimate + anchorMode: AnchorMode.Any, +}; + +const transaction = await makeSTXTokenTransfer(txOptions); + +// to see the raw serialized tx +const serializedTx = transaction.serialize(); // Uint8Array +const serializedTxHex = bytesToHex(serializedTx); // hex string + +// broadcasting transaction to the specified network +const broadcastResponse = await broadcastTransaction(transaction); +const txId = broadcastResponse.txid; +``` + +## Smart Contract Deploy Transaction + +```typescript +import { makeContractDeploy, broadcastTransaction, AnchorMode } from '@stacks/transactions'; +import { StacksTestnet, StacksMainnet } from '@stacks/network'; +import { readFileSync } from 'fs'; + +// for mainnet, use `StacksMainnet()` +const network = new StacksTestnet(); + +const txOptions = { + contractName: 'contract_name', + codeBody: readFileSync('/path/to/contract.clar').toString(), + senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', + network, + anchorMode: AnchorMode.Any, +}; + +const transaction = await makeContractDeploy(txOptions); + +const broadcastResponse = await broadcastTransaction(transaction, network); +const txId = broadcastResponse.txid; +``` + +## Smart Contract Function Call + +```typescript +import { + makeContractCall, + broadcastTransaction, + AnchorMode, + FungibleConditionCode, + makeStandardSTXPostCondition, + bufferCVFromString, +} from '@stacks/transactions'; +import { StacksTestnet, StacksMainnet } from '@stacks/network'; + +// for mainnet, use `StacksMainnet()` +const network = new StacksTestnet(); + +// Add an optional post condition +// See below for details on constructing post conditions +const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE'; +const postConditionCode = FungibleConditionCode.GreaterEqual; +const postConditionAmount = 1000000n; +const postConditions = [ + makeStandardSTXPostCondition(postConditionAddress, postConditionCode, postConditionAmount), +]; + +const txOptions = { + contractAddress: 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X', + contractName: 'contract_name', + functionName: 'contract_function', + functionArgs: [bufferCVFromString('foo')], + senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', + validateWithAbi: true, + network, + postConditions, + anchorMode: AnchorMode.Any, +}; + +const transaction = await makeContractCall(txOptions); + +const broadcastResponse = await broadcastTransaction(transaction, network); +const txId = broadcastResponse.txid; +``` + +In this example we construct a `contract-call` transaction with a post condition. We have set the `validateWithAbi` option to `true`, so the `makeContractCall` builder will attempt to fetch this contracts ABI from the specified Stacks network, and validate that the provided functionArgs match what is described in the ABI. This should help you avoid constructing invalid contract-call transactions. If you would prefer to provide your own ABI instead of fetching it from the network, the `validateWithABI` option also accepts [ClarityABI](https://github.com/blockstack/stacks-transactions-js/blob/master/src/contract-abi.ts#L231) objects, which can be constructed from ABI files like so: + +```typescript +import { ClarityAbi } from '@stacks/transactions'; +import { readFileSync } from 'fs'; + +const abi: ClarityAbi = JSON.parse(readFileSync('abi.json').toString()); +// For sample abi json see: stacks.js/packages/transactions/tests/abi/test-abi.json +``` + +## Sponsoring Transactions + +To generate a sponsored transaction, first create and sign the transaction as the origin. The `sponsored` property in the options object must be set to true. + +```typescript +import { bytesToHex } from '@stacks/common'; +import { makeContractCall, BufferCV, AnchorMode, bufferCVFromString } from '@stacks/transactions'; + +const txOptions = { + contractAddress: 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X', + contractName: 'contract_name', + functionName: 'contract_function', + functionArgs: [bufferCVFromString('foo')], + fee: 0, + senderKey: 'b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01', + validateWithAbi: true, + sponsored: true, + anchorMode: AnchorMode.Any, +}; + +const transaction = await makeContractCall(txOptions); +const serializedTx = bytesToHex(transaction.serialize()); +``` + +The serialized transaction can now be passed to the sponsoring party which will sign the sponsor portion of the transaction and set the fee. + +```typescript +import { + sponsorTransaction, + BytesReader, + deserializeTransaction, + broadcastTransaction, +} from '@stacks/transactions'; +import { StacksTestnet, StacksMainnet } from '@stacks/network'; + +const bytesReader = new BytesReader(Buffer.from(serializedTx, 'hex')); +const deserializedTx = deserializeTransaction(bytesReader); +const sponsorKey = '770287b9471081c8acd37d57190c7a70f0da2633311cc120853537362d32e67c01'; +const fee = 1000n; + +const sponsorOptions = { + transaction: deserializedTx, + sponsorPrivateKey: sponsorKey, + fee, + sponsorNonce: 0, +}; + +const sponsoredTx = await sponsorTransaction(sponsorOptions); + +// for mainnet, use `StacksMainnet()` +const network = new StacksTestnet(); + +const broadcastResponse = await broadcastTransaction(sponsoredTx, network); +const txId = broadcastResponse.txid; +``` + +## Supporting multi-signature transactions + +To generate a multi-sig transaction, first create an unsigned transaction. +The `numSignatures` and `publicKeys` properties in the options object must be set: + +```typescript +import { + makeUnsignedSTXTokenTransfer, + createStacksPrivateKey, + deserializeTransaction, + pubKeyfromPrivKey, + publicKeyToString, + TransactionSigner, + standardPrincipalCV, + BytesReader, + AnchorMode, +} from '@stacks/transactions'; + +const recipient = standardPrincipalCV('SP3FGQ8...'); +const amount = 2500000n; +const fee = 0n; +const memo = 'test memo'; + +// private keys of the participants in the transaction +const privKeyStrings = ['6d430bb9...', '2a584d89...', 'd5200dee...']; + +// create private key objects from string array +const privKeys = privKeyStrings.map(createStacksPrivateKey); + +// corresponding public keys +const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); + +// create public key string array from objects +const pubKeyStrings = pubKeys.map(publicKeyToString); + +const transaction = await makeUnsignedSTXTokenTransfer({ + recipient, + amount, + fee, + memo, + numSignatures: 2, // number of signature required + publicKeys: pubKeyStrings, // public key string array with >= numSignatures elements + anchorMode: AnchorMode.Any, +}); + +const serializedTx = transaction.serialize(); +``` + +This transaction payload can be passed along to other participants to sign. In addition to +meeting the numSignatures requirement, the public keys of the parties who did not sign the +transaction must be appended to the signature. + +```typescript +// deserialize and sign transaction +const bytesReader = new BytesReader(serializedTx); +// Partially signed or unsigned multi-sig tx can be deserialized to add the required signatures +const deserializedTx = deserializeTransaction(bytesReader); + +const signer = new TransactionSigner(deserializedTx); + +// first signature +signer.signOrigin(privKeys[0]); + +// second signature +signer.signOrigin(privKeys[1]); + +// after meeting the numSignatures requirement, the public +// keys of the participants who did not sign must be appended +signer.appendOrigin(pubKeys[2]); + +// the serialized multi-sig tx +const serializedSignedTx = deserializedTx.serialize(); +``` + +## Calling Read-only Contract Functions + +Read-only contract functions can be called without generating or broadcasting a transaction. Instead it works via a direct API call to a Stacks node. + +```typescript +import { bufferCVFromString, callReadOnlyFunction } from '@stacks/transactions'; +import { StacksTestnet } from '@stacks/network'; + +const contractAddress = 'ST3KC0MTNW34S1ZXD36JYKFD3JJMWA01M55DSJ4JE'; +const contractName = 'kv-store'; +const functionName = 'get-value'; +const buffer = bufferCVFromString('foo'); +const network = new StacksTestnet(); +const senderAddress = 'ST2F4BK4GZH6YFBNHYDDGN4T1RKBA7DA1BJZPJEJJ'; + +const options = { + contractAddress, + contractName, + functionName, + functionArgs: [buffer], + network, + senderAddress, +}; + +const result = await callReadOnlyFunction(options); +``` + +## Constructing Clarity Values + +Building transactions that call functions in deployed clarity contracts requires you to construct valid Clarity Values to pass to the function as arguments. The [Clarity type system](https://github.com/stacksgov/sips/blob/master/sip/sip-002-smart-contract-language.md#clarity-type-system) contains the following types: + +- `(tuple (key-name-0 key-type-0) (key-name-1 key-type-1) ...)` + - a typed tuple with named fields. +- `(list max-len entry-type)` + - a list of maximum length max-len, with entries of type entry-type +- `(response ok-type err-type)` + - object used by public functions to commit their changes or abort. May be returned or used by other functions as well, however, only public functions have the commit/abort behavior. +- `(optional some-type)` + - an option type for objects that can either be (some value) or none +- `(buff max-len)` + - byte buffer or maximum length max-len. +- `principal` + - object representing a principal (whether a contract principal or standard principal). +- `bool` + - boolean value ('true or 'false) +- `int` + - signed 128-bit integer +- `uint` + - unsigned 128-bit integer + +This library contains Typescript types and classes that map to the Clarity types, in order to make it easy to construct well-typed Clarity values in Javascript. These types all extend the abstract class `ClarityValue`. + +```typescript +import { + trueCV, + falseCV, + noneCV, + someCV, + intCV, + uintCV, + standardPrincipalCV, + contractPrincipalCV, + responseErrorCV, + responseOkCV, + listCV, + tupleCV, + bufferCV, +} from '@stacks/transactions'; +import { utf8ToBytes } from '@stacks/common'; + +// construct boolean clarity values +const t = trueCV(); +const f = falseCV(); + +// construct optional clarity values +const nothing = noneCV(); +const something = someCV(t); + +// construct a buffer clarity value from an existing byte array +const bytes = utf8ToBytes('foo'); // Uint8Array(3) [ 102, 111, 111 ] +const bufCV = bufferCV(bytes); + +// construct signed and unsigned integer clarity values +const i = intCV(-10); +const u = uintCV(10); + +// construct principal clarity values +const address = 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B'; +const contractName = 'contract-name'; +const spCV = standardPrincipalCV(address); +const cpCV = contractPrincipalCV(address, contractName); + +// construct response clarity values +const errCV = responseErrorCV(trueCV()); +const okCV = responseOkCV(falseCV()); + +// construct tuple clarity values +const tupCV = tupleCV({ + a: intCV(1), + b: trueCV(), + c: falseCV(), +}); + +// construct list clarity values +const l = listCV([trueCV(), falseCV()]); +``` + +If you develop in Typescript, the type checker can help prevent you from creating wrongly-typed Clarity values. For example, the following code won't compile since in Clarity lists are homogeneous, meaning they can only contain values of a single type. It is important to include the type variable `BooleanCV` in this example, otherwise the typescript type checker won't know which type the list is of and won't enforce homogeneity. + +```typescript +const l = listCV([trueCV(), intCV(1)]); +``` + +## Post Conditions + +Three types of post conditions can be added to transactions: + +1. STX post condition +2. Fungible token post condition +3. Non-Fungible token post condition + +For details see: https://github.com/stacksgov/sips/blob/main/sips/sip-005/sip-005-blocks-and-transactions.md#transaction-post-conditions + +## STX post condition + +```typescript +import { + FungibleConditionCode, + makeStandardSTXPostCondition, + makeContractSTXPostCondition, +} from '@stacks/transactions'; + +// With a standard principal +const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE'; +const postConditionCode = FungibleConditionCode.GreaterEqual; +const postConditionAmount = 12345n; + +const standardSTXPostCondition = makeStandardSTXPostCondition( + postConditionAddress, + postConditionCode, + postConditionAmount +); + +// With a contract principal +const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X'; +const contractName = 'test-contract'; + +const contractSTXPostCondition = makeContractSTXPostCondition( + contractAddress, + contractName, + postConditionCode, + postConditionAmount +); +``` + +## Fungible token post condition + +```typescript +import { + FungibleConditionCode, + createAssetInfo, + makeStandardFungiblePostCondition, +} from '@stacks/transactions'; + +// With a standard principal +const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE'; +const postConditionCode = FungibleConditionCode.GreaterEqual; +const postConditionAmount = 12345n; +const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ'; +const assetContractName = 'test-asset-contract'; +const assetName = 'test-token'; +const fungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName); + +const standardFungiblePostCondition = makeStandardFungiblePostCondition( + postConditionAddress, + postConditionCode, + postConditionAmount, + fungibleAssetInfo +); + +// With a contract principal +const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X'; +const contractName = 'test-contract'; +const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ'; +const assetContractName = 'test-asset-contract'; +const assetName = 'test-token'; +const fungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName); + +const contractFungiblePostCondition = makeContractFungiblePostCondition( + contractAddress, + contractName, + postConditionCode, + postConditionAmount, + fungibleAssetInfo +); +``` + +## Non-fungible token post condition + +> **Warning** +> The Stacks blockchain's post-condition processor can NOT check ownership. +> It checks whether or not a principal **will send** or **will not send** an NFT. +> Post-conditions can NOT verify anything about the recipient of an asset. +> If you want to verify conditions about asset recipients, you will need to use [Clarity](https://docs.stacks.co/docs/write-smart-contracts/). + +```typescript +import { + NonFungibleConditionCode, + createAssetInfo, + makeStandardNonFungiblePostCondition, + makeContractNonFungiblePostCondition, + bufferCVFromString, +} from '@stacks/transactions'; + +// With a standard principal +const postConditionAddress = 'SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE'; +const postConditionCode = NonFungibleConditionCode.DoesNotSend; +const assetAddress = 'SP62M8MEFH32WGSB7XSF9WJZD7TQB48VQB5ANWSJ'; +const assetContractName = 'test-asset-contract'; +const assetName = 'test-asset'; +const assetId = bufferCVFromString('test-token-asset-id'); +const nonFungibleAssetInfo = createAssetInfo(assetAddress, assetContractName, assetName); + +const standardNonFungiblePostCondition = makeStandardNonFungiblePostCondition( + postConditionAddress, + postConditionCode, + nonFungibleAssetInfo, + assetId +); + +// With a contract principal +const contractAddress = 'SPBMRFRPPGCDE3F384WCJPK8PQJGZ8K9QKK7F59X'; +const contractName = 'test-contract'; + +const contractNonFungiblePostCondition = makeContractNonFungiblePostCondition( + contractAddress, + contractName, + postConditionCode, + nonFungibleAssetInfo, + assetId +); +``` + +## Conversion of Clarity Values to JSON + +Clarity Values represent values of Clarity contracts. If a JSON format is required the helper function `cvToJSON` can be used. + +```typescript +import { cvToJSON, hexToCV } from '@stacks/transactions'; + +cvToJSON(hexToCV(tx.tx_result.hex)); +``` \ No newline at end of file diff --git a/public/string-meme.png b/public/string-meme.png new file mode 100644 index 000000000..bf06a5206 Binary files /dev/null and b/public/string-meme.png differ