diff --git a/example/package-lock.json b/example/package-lock.json
index c5efdaa..192d779 100644
--- a/example/package-lock.json
+++ b/example/package-lock.json
@@ -11,6 +11,7 @@
"@mantine/core": "^7.11.2",
"@mantine/hooks": "^7.11.2",
"@stacks/common": "^7.0.2",
+ "@stacks/stacking": "^7.0.2",
"@stacks/transactions": "^7.0.2",
"@tanstack/react-query": "^5.50.1",
"bip322-js": "^2.0.0",
@@ -1415,11 +1416,74 @@
"win32"
]
},
+ "node_modules/@scure/base": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
+ "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ]
+ },
+ "node_modules/@scure/bip39": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz",
+ "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "@noble/hashes": "~1.1.1",
+ "@scure/base": "~1.1.0"
+ }
+ },
+ "node_modules/@scure/bip39/node_modules/@noble/hashes": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz",
+ "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ]
+ },
"node_modules/@stacks/common": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@stacks/common/-/common-7.0.2.tgz",
"integrity": "sha512-+RSecHdkxOtswmE4tDDoZlYEuULpnTQVeDIG5eZ32opK8cFxf4EugAcK9CsIsHx/Se1yTEaQ21WGATmJGK84lQ=="
},
+ "node_modules/@stacks/encryption": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-7.0.2.tgz",
+ "integrity": "sha512-3evRvxPqVzQAhcZ8uacQrVfAETUMIV8VyKkHGsd4QZroGWlvXQheLV3CFeDttFb304QcKq/oKv1clOvQ2shaAw==",
+ "dependencies": {
+ "@noble/hashes": "1.1.5",
+ "@noble/secp256k1": "1.7.1",
+ "@scure/bip39": "1.1.0",
+ "@stacks/common": "^7.0.2",
+ "base64-js": "^1.5.1",
+ "bs58": "^5.0.0",
+ "ripemd160-min": "^0.0.6",
+ "varuint-bitcoin": "^1.1.2"
+ }
+ },
+ "node_modules/@stacks/encryption/node_modules/@noble/hashes": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz",
+ "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ]
+ },
"node_modules/@stacks/network": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@stacks/network/-/network-7.0.2.tgz",
@@ -1429,6 +1493,37 @@
"cross-fetch": "^3.1.5"
}
},
+ "node_modules/@stacks/stacking": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@stacks/stacking/-/stacking-7.0.2.tgz",
+ "integrity": "sha512-JAi396fKMgA0v8Lrj6yYNKRBuPHT+dq1/vVs1GCpgbH74ZHQT6NQYBrsnIxtc85M9w86zMr1FHrIq66Z0kKh/A==",
+ "dependencies": {
+ "@noble/hashes": "1.1.5",
+ "@scure/base": "1.1.1",
+ "@stacks/common": "^7.0.2",
+ "@stacks/encryption": "^7.0.2",
+ "@stacks/network": "^7.0.2",
+ "@stacks/stacks-blockchain-api-types": "^0.61.0",
+ "@stacks/transactions": "^7.0.2",
+ "bs58": "^5.0.0"
+ }
+ },
+ "node_modules/@stacks/stacking/node_modules/@noble/hashes": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz",
+ "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ]
+ },
+ "node_modules/@stacks/stacks-blockchain-api-types": {
+ "version": "0.61.0",
+ "resolved": "https://registry.npmjs.org/@stacks/stacks-blockchain-api-types/-/stacks-blockchain-api-types-0.61.0.tgz",
+ "integrity": "sha512-yPOfTUboo5eA9BZL/hqMcM71GstrFs9YWzOrJFPeP4cOO1wgYvAcckgBRbgiE3NqeX0A7SLZLDAXLZbATuRq9w=="
+ },
"node_modules/@stacks/transactions": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-7.0.2.tgz",
@@ -2028,7 +2123,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -5684,6 +5778,14 @@
"inherits": "^2.0.1"
}
},
+ "node_modules/ripemd160-min": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/ripemd160-min/-/ripemd160-min-0.0.6.tgz",
+ "integrity": "sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/rollup": {
"version": "4.18.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz",
diff --git a/example/package.json b/example/package.json
index 7ced537..93e11f8 100644
--- a/example/package.json
+++ b/example/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^7.11.2",
"@mantine/hooks": "^7.11.2",
"@stacks/common": "^7.0.2",
+ "@stacks/stacking": "^7.0.2",
"@stacks/transactions": "^7.0.2",
"@tanstack/react-query": "^5.50.1",
"bip322-js": "^2.0.0",
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 29b0341..2496eb7 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -35,6 +35,7 @@ import { NetworkSelector } from './components/NetworkSelector';
import { SendSip10 } from './components/stacks/SendSip10';
import { SendStx } from './components/stacks/SendStx';
import { SignTransaction } from './components/stacks/SignTransaction.tsx';
+import { SignTransactions } from './components/stacks/SignTransactions/index.tsx';
import TransferRunes from './components/transferRunes/index.tsx';
import { GetPermissions } from './components/wallet/GetPermissions.tsx';
import { WalletType } from './components/wallet/WalletType';
@@ -329,6 +330,7 @@ const StacksMethods = () => {
{stxAddressInfo?.[0]?.publicKey ? (
) : null}
+
>
);
};
diff --git a/example/src/components/bitcoin/GetAccounts.tsx b/example/src/components/bitcoin/GetAccounts.tsx
index d08d2d5..9031404 100644
--- a/example/src/components/bitcoin/GetAccounts.tsx
+++ b/example/src/components/bitcoin/GetAccounts.tsx
@@ -1,11 +1,7 @@
import { useQuery } from '@tanstack/react-query';
import Wallet, { AddressPurpose } from 'sats-connect';
-import styled from 'styled-components';
import { Button, Card } from '../../App.styles';
-
-const ErrorMessage = styled.div({
- color: 'red',
-});
+import { ErrorMessage } from '../common';
export function GetAccounts() {
const { refetch, error, data, isFetching, isError, isSuccess } = useQuery({
diff --git a/example/src/components/bitcoin/GetAddresses.tsx b/example/src/components/bitcoin/GetAddresses.tsx
index 4a469ac..7589227 100644
--- a/example/src/components/bitcoin/GetAddresses.tsx
+++ b/example/src/components/bitcoin/GetAddresses.tsx
@@ -1,11 +1,7 @@
import { useQuery } from '@tanstack/react-query';
import Wallet, { AddressPurpose } from 'sats-connect';
-import styled from 'styled-components';
import { Button, Card } from '../../App.styles';
-
-const ErrorMessage = styled.div({
- color: 'red',
-});
+import { ErrorMessage } from '../common';
export function GetAddresses() {
const { refetch, error, data, isFetching, isError, isSuccess } = useQuery({
diff --git a/example/src/components/common.tsx b/example/src/components/common.tsx
new file mode 100644
index 0000000..e327380
--- /dev/null
+++ b/example/src/components/common.tsx
@@ -0,0 +1,5 @@
+import styled from 'styled-components';
+
+export const ErrorMessage = styled.div({
+ color: 'red',
+});
diff --git a/example/src/components/stacks/SignTransactions/index.tsx b/example/src/components/stacks/SignTransactions/index.tsx
new file mode 100644
index 0000000..94d57e5
--- /dev/null
+++ b/example/src/components/stacks/SignTransactions/index.tsx
@@ -0,0 +1,81 @@
+import { Button, Card, Checkbox, Stack, Switch } from '@mantine/core';
+import { useMutation } from '@tanstack/react-query';
+import { useState } from 'react';
+import { ErrorMessage } from '../../common';
+import { mutationFunction } from './mutationFunction';
+
+export interface Props {
+ publicKey: string;
+}
+
+function buttonText(isPending: boolean) {
+ return isPending ? 'Signing transactions...' : 'Sign transactions';
+}
+
+export function SignTransactions({ publicKey }: Props) {
+ // Checkboxes
+ const [isPoolAllowContractSelected, setIsPoolAllowContractSelected] = useState(false);
+ const [isPoolDelegateStacksSelected, setIsPoolDelegateStacks] = useState(false);
+ const [isContractDeploySelected, setIsContractDeploySelected] = useState(false);
+ const [isTokenTransferSelected, setIsTokenTransferSelected] = useState(false);
+
+ const [broadcast, setBroadcast] = useState(true);
+
+ const signTransactionsMutation = useMutation({
+ mutationFn: mutationFunction,
+ });
+
+ function handleSignTransactionsClick() {
+ signTransactionsMutation
+ .mutateAsync({
+ isPoolAllowContractSelected,
+ isPoolDelegateStacksSelected,
+ isContractDeploySelected,
+ isTokenTransferSelected,
+ broadcast,
+ publicKey,
+ })
+ .then(console.log)
+ .catch((error: unknown) => {
+ console.error(error);
+ if (error instanceof Error) console.error(error.cause);
+ });
+ }
+
+ return (
+
+ Sign transactions
+
+ setIsPoolAllowContractSelected(!isPoolAllowContractSelected)}
+ />
+ setIsPoolDelegateStacks(!isPoolDelegateStacksSelected)}
+ />
+ setIsContractDeploySelected(!isContractDeploySelected)}
+ />
+ setIsTokenTransferSelected(!isTokenTransferSelected)}
+ />
+ setBroadcast(!broadcast)} />
+
+
+
+ {signTransactionsMutation.isError && !signTransactionsMutation.isPending && (
+ Failed to sign transactions. Check console for details.
+ )}
+
+
+ );
+}
diff --git a/example/src/components/stacks/SignTransactions/mutationFunction.ts b/example/src/components/stacks/SignTransactions/mutationFunction.ts
new file mode 100644
index 0000000..d35d5a7
--- /dev/null
+++ b/example/src/components/stacks/SignTransactions/mutationFunction.ts
@@ -0,0 +1,111 @@
+import { request } from '@sats-connect/core';
+import { poxAddressToTuple } from '@stacks/stacking';
+import {
+ contractPrincipalCV,
+ makeUnsignedContractCall,
+ makeUnsignedContractDeploy,
+ makeUnsignedSTXTokenTransfer,
+ noneCV,
+ standardPrincipalCV,
+ uintCV,
+} from '@stacks/transactions';
+
+const helloWorldContractBody = `
+(define-data-var greeting (string-ascii 100) "Hello, World!")
+
+(define-read-only (get-greeting)
+ (ok (var-get greeting))
+)
+
+(define-public (set-greeting (new-greeting (string-ascii 100)))
+ (begin
+ (var-set greeting new-greeting)
+ (ok new-greeting))
+)
+`;
+
+export const poxContractAddress = 'SP000000000000000000002Q6VF78';
+export const poxContractName = 'pox-4';
+export const poolContractAddress = 'SP001SFSMC2ZY76PD4M68P3WGX154XCH7NE3TYMX';
+export const poolContractName = 'pox4-pools';
+export const poolAdminStacksAddress = 'SPXVRSEH2BKSXAEJ00F1BY562P45D5ERPSKR4Q33';
+export const poolAdminPoxAddress = 'bc1qmv2pxw5ahvwsu94kq5f520jgkmljs3af8ly6tr';
+
+export interface MutationFnArgs {
+ isPoolAllowContractSelected: boolean;
+ isPoolDelegateStacksSelected: boolean;
+ isContractDeploySelected: boolean;
+ isTokenTransferSelected: boolean;
+ broadcast: boolean;
+ publicKey: string;
+}
+
+export async function mutationFunction({
+ isPoolAllowContractSelected,
+ isPoolDelegateStacksSelected,
+ isContractDeploySelected,
+ isTokenTransferSelected,
+ broadcast,
+ publicKey,
+}: MutationFnArgs) {
+ const transactions: string[] = [];
+
+ if (isPoolAllowContractSelected) {
+ const transaction = await makeUnsignedContractCall({
+ contractAddress: poxContractAddress,
+ contractName: poxContractName,
+ functionName: 'allow-contract-caller',
+ functionArgs: [contractPrincipalCV(poolContractAddress, poolContractName), noneCV()],
+ publicKey,
+ });
+ transactions.push(transaction.serialize());
+ }
+
+ if (isPoolDelegateStacksSelected) {
+ const transaction = await makeUnsignedContractCall({
+ contractAddress: poolContractAddress,
+ contractName: poolContractName,
+ functionName: 'delegate-stx',
+ functionArgs: [
+ uintCV(1234567890),
+ standardPrincipalCV(poolAdminStacksAddress),
+ noneCV(),
+ noneCV(),
+ poxAddressToTuple(poolAdminPoxAddress),
+ noneCV(),
+ ],
+ publicKey,
+ });
+ transactions.push(transaction.serialize());
+ }
+
+ if (isContractDeploySelected) {
+ const now = new Date().getTime();
+ const transaction = await makeUnsignedContractDeploy({
+ contractName: `hello-world-${now}`,
+ codeBody: helloWorldContractBody,
+ publicKey,
+ });
+ transactions.push(transaction.serialize());
+ }
+
+ if (isTokenTransferSelected) {
+ const transaction = await makeUnsignedSTXTokenTransfer({
+ recipient: 'SP1VYV2JBF1QPNDSKHBZRAWRC4KQXP8ZSSRNKPJE4', // acc 4
+ amount: '100000', // 0.1 STX
+ publicKey,
+ });
+ transactions.push(transaction.serialize());
+ }
+
+ const res = await request('stx_signTransactions', {
+ transactions,
+ broadcast,
+ });
+
+ if (res.status === 'error') {
+ throw new Error('Error signing transactions', { cause: res.error });
+ }
+
+ return res.result;
+}
diff --git a/example/src/components/wallet/GetPermissions.tsx b/example/src/components/wallet/GetPermissions.tsx
index d658c83..b722bde 100644
--- a/example/src/components/wallet/GetPermissions.tsx
+++ b/example/src/components/wallet/GetPermissions.tsx
@@ -1,11 +1,7 @@
import { useQuery } from '@tanstack/react-query';
import Wallet from 'sats-connect';
-import styled from 'styled-components';
import { Button, Card } from '../../App.styles';
-
-const ErrorMessage = styled.div({
- color: 'red',
-});
+import { ErrorMessage } from '../common';
export function GetPermissions() {
const { refetch, error, data, isFetching, isError, isSuccess } = useQuery({
diff --git a/example/src/components/wallet/WalletType.tsx b/example/src/components/wallet/WalletType.tsx
index fa35de4..f3148c6 100644
--- a/example/src/components/wallet/WalletType.tsx
+++ b/example/src/components/wallet/WalletType.tsx
@@ -1,11 +1,7 @@
import { useQuery } from '@tanstack/react-query';
import Wallet from 'sats-connect';
-import styled from 'styled-components';
import { Button, Card } from '../../App.styles';
-
-const ErrorMessage = styled.div({
- color: 'red',
-});
+import { ErrorMessage } from '../common';
export function WalletType() {
const { refetch, error, data, isFetching, isError, isSuccess } = useQuery({