Skip to content
This repository has been archived by the owner on Nov 15, 2024. It is now read-only.

connect kit example #31

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
514 changes: 462 additions & 52 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
"dependencies": {
"@scure/base": "^1.1.1",
"@scure/btc-signer": "1.1.0",
"@stacks/connect": "^7.8.0",
"@stacks/transactions": "^6.12.0",
"@types/node": "^20.4.9",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"sats-connect": "2.1.0",
"sats-connect": "2.3.1-c01c240",
"typescript": "^4.9.5"
},
"scripts": {
Expand Down
133 changes: 79 additions & 54 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { Capability } from "sats-connect";
import {
import Wallet, {
AddressPurpose,
BitcoinNetworkType,
getAddress,
getCapabilities,
getProviders,
request,
} from "sats-connect";

import CreateFileInscription from "./components/createFileInscription";
Expand All @@ -30,6 +29,9 @@ import { useEffect, useMemo, useState } from "react";
import "./App.css";
import CreateRepeatInscriptions from "./components/createRepeatInscriptions";
import SignBulkTransaction from "./components/signBulkTransaction";
import GetRunesBalance from "./components/getRuneBalance";
import Airdrop from "./components/stacks/airdrop";
import { AppConfig, showConnect, UserSession } from "@stacks/connect";

function App() {
const [paymentAddress, setPaymentAddress] = useLocalStorage("paymentAddress");
Expand Down Expand Up @@ -89,13 +91,14 @@ function App() {
}, [network]);

const isReady =
!!capabilities &&
!!paymentAddress &&
!!paymentPublicKey &&
!!ordinalsAddress &&
!!ordinalsPublicKey &&
!!stacksAddress;
!!ordinalsPublicKey;

const onWalletDisconnect = () => {
Wallet.disconnect();
setPaymentAddress(undefined);
setPaymentPublicKey(undefined);
setOrdinalsAddress(undefined);
Expand All @@ -105,7 +108,7 @@ function App() {

const handleGetInfo = async () => {
try {
const response = await request("getInfo", null);
const response = await Wallet.request("getInfo", null);

if (response.status === "success") {
alert("Success. Check console for response");
Expand Down Expand Up @@ -165,12 +168,15 @@ function App() {
};

const onConnectAccountClick = async () => {
const response = await request('getAccounts', {
purposes: [AddressPurpose.Ordinals, AddressPurpose.Payment, AddressPurpose.Stacks],
message: 'SATS Connect Demo',
const response = await Wallet.request("getAccounts", {
purposes: [
AddressPurpose.Ordinals,
AddressPurpose.Payment,
AddressPurpose.Stacks,
],
message: "SATS Connect Demo",
});
console.log("getAccounts ~ response:", response)
if (response.status === 'success') {
if (response.status === "success") {
const paymentAddressItem = response.result.find(
(address) => address.purpose === AddressPurpose.Payment
);
Expand All @@ -194,7 +200,7 @@ function App() {
console.error(response.error);
}
}
}
};

const capabilityMessage =
capabilityState === "loading"
Expand All @@ -207,35 +213,40 @@ function App() {
? "Something went wrong with getting capabilities"
: undefined;

if (capabilityMessage) {
return (
<div style={{ padding: 30 }}>
<h1>Sats Connect Test App - {network}</h1>
<div>{capabilityMessage}</div>
</div>
);
}
// if (capabilityMessage) {
// return (
// <div style={{ padding: 30 }}>
// <h1>Sats Connect Test App - {network}</h1>
// <div>{capabilityMessage}</div>
// </div>
// );
// }

const handleConnectStacks = async () => {
const appConfig = new AppConfig(['store_write', 'publish_data']);
const userSession = new UserSession({ appConfig });
const appDetails = {
name: "Sats Connect Test App",
icon: "",
}

showConnect({
appDetails,
userSession,
onFinish: () => {
window.location.reload();
},
onCancel: () => {
console.log('oops');
},
});
};

if (!isReady) {
return (
<div style={{ padding: 30 }}>
<h1>Sats Connect Test App - {network}</h1>
<div>Please connect your wallet to continue</div>
<h2>Available Wallets</h2>
<div>
{providers
? providers.map((provider) => (
<button
key={provider.id}
className="provider"
onClick={() => window.open(provider.chromeWebStoreUrl)}
>
<img className="providerImg" src={provider.icon} />
<p className="providerName">{provider.name}</p>
</button>
))
: null}
</div>
<div style={{ background: "lightgray", padding: 30, marginTop: 10 }}>
<button style={{ height: 30, width: 180 }} onClick={toggleNetwork}>
Switch Network
Expand All @@ -245,9 +256,19 @@ function App() {
<button style={{ height: 30, width: 180 }} onClick={onConnectClick}>
Connect
</button>
<button style={{ height: 30, width: 180, marginLeft: 10 }} onClick={onConnectAccountClick}>
<button
style={{ height: 30, width: 180, marginLeft: 10 }}
onClick={onConnectAccountClick}
>
Connect Account
</button>
<button
style={{ height: 30, width: 180, marginLeft: 10 }}
onClick={handleConnectStacks}
>
Connect Stacks
</button>
<Airdrop network={network} />
</div>
</div>
);
Expand Down Expand Up @@ -300,6 +321,7 @@ function App() {
network={network}
capabilities={capabilities!}
/>
<GetRunesBalance />

<CreateTextInscription network={network} capabilities={capabilities!} />

Expand All @@ -310,32 +332,35 @@ function App() {

<CreateFileInscription network={network} capabilities={capabilities!} />
</div>
{stacksAddress && (
<>
<h2>Stacks</h2>
<div>
<p>Stacks Address: {stacksAddress}</p>
<p>Stacks PubKey: {stacksPublicKey}</p>
<br />
<Airdrop network={network} />
<StxGetAccounts />

<h2>Stacks</h2>
<div>
<p>Stacks Address: {stacksAddress}</p>
<p>Stacks PubKey: {stacksPublicKey}</p>
<br />
<StxGetAddresses />

<StxGetAccounts />
<StxTransferStx address={stacksAddress} />

<StxGetAddresses />
<StxSignTransaction
network={network}
publicKey={stacksPublicKey || ""}
/>

<StxTransferStx address={stacksAddress} />
<StxCallContract network={network} />

<StxSignTransaction
network={network}
publicKey={stacksPublicKey || ""}
/>

<StxCallContract network={network} />
<StxSignMessage network={network} />

<StxSignMessage network={network} />
<StxSignStructuredMessage network={network} />

<StxSignStructuredMessage network={network} />

<StxDeployContract network={network} />
</div>
<StxDeployContract network={network} />
</div>
</>
)}
</div>
);
}
Expand Down
27 changes: 27 additions & 0 deletions src/components/getRuneBalance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Wallet from "sats-connect";

const GetRunesBalance = () => {
const getBalance = async () => {
try {
const response = await Wallet.request("runes_getBalance", null);
if (response.status === "success") {
alert("Success. Check console for response");
console.log(response.result);
} else {
alert("Error getting runes balance. Check console for error logs");
console.error(response.error);
}
} catch (err) {
console.log(err);
}
};


return (
<div className="container">
<button onClick={getBalance}>Get Runes Balance</button>
</div>
);
};

export default GetRunesBalance;
4 changes: 2 additions & 2 deletions src/components/sendBitcoin.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import type { Capability } from "sats-connect";
import {
import Wallet, {
BitcoinNetworkType,
RpcErrorCode,
request,
Expand Down Expand Up @@ -41,7 +41,7 @@ const SendBitcoin = ({ network, address, capabilities }: Props) => {

const onSendBtcRpc = async () => {
try {
const response = await request("sendTransfer", {
const response = await Wallet.request("sendTransfer", {
recipients: [
{
address: recipient,
Expand Down
5 changes: 2 additions & 3 deletions src/components/signMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useState } from "react";
import type { Capability } from "sats-connect";
import {
import Wallet, {
BitcoinNetworkType,
RpcErrorCode,
request,
signMessage,
} from "sats-connect";

Expand Down Expand Up @@ -36,7 +35,7 @@ const SignMessage = ({ network, address, capabilities }: Props) => {

const onSignMessageRpcClick = async () => {
try {
const response = await request("signMessage", {
const response = await Wallet.request("signMessage", {
address,
message,
});
Expand Down
87 changes: 87 additions & 0 deletions src/components/stacks/airdrop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { BitcoinNetworkType, request } from "sats-connect";

import {
AnchorMode,
listCV,
makeContractCall,
PostConditionMode,
stringUtf8CV,
} from "@stacks/transactions";

import nakamotoAirdrop from "./xverse_nakamoto_airdrop.json";

import { openContractCall } from "@stacks/connect";
import { useEffect, useState } from "react";

const AIRDROP_CONTRACT =
"SP3F7GQ48JY59521DZEE6KABHBF4Q33PEYJ823ZXQ";
const CONTRACT_NAME = "xverse-nakamoto";
function splitArray<T>(array: T[], maxLength: number): [T[], T[]] {
const firstArray = array.slice(0, maxLength);
const secondArray = array.slice(maxLength, maxLength * 2);
return [firstArray, secondArray];
}

type Props = {
network: BitcoinNetworkType;
};

function Airdrop({ network }: Props) {
const [airdropAddresses, setAirdropAddresses] = useState<string[]>([]);

useEffect(() => {
const addresses = nakamotoAirdrop.map(
(item: any) => Object.values(item)[0]
);
setAirdropAddresses(addresses as any);
}, []);
const handleWebBtcCallContractClick = async () => {
const [firstChunk, secondChunk] = splitArray(airdropAddresses, 4999);
const addressList1 = listCV(
firstChunk.map((address) => stringUtf8CV(address))
);
const addressList2 = listCV(
secondChunk.map((address) => stringUtf8CV(address))
);
openContractCall({
network: network === BitcoinNetworkType.Mainnet ? "mainnet" : "testnet",
anchorMode: AnchorMode.Any,

contractAddress: AIRDROP_CONTRACT,
contractName: CONTRACT_NAME,
functionName: "airdrop",
functionArgs: [addressList1, addressList2, listCV([])],
postConditionMode: PostConditionMode.Allow, // whether the tx should fail when unexpected assets are transferred
onFinish: (response) => {
// WHEN user confirms pop-up
console.log(response);
},
onCancel: () => {
// WHEN user cancels/closes pop-up
},
});
};

return (
<div className="container">
<h3>Call contract</h3>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
}}
>
<label>
<div>Contract name:</div>
<div>{AIRDROP_CONTRACT}.{CONTRACT_NAME}</div>
</label>
<button onClick={handleWebBtcCallContractClick}>
Mint Airdrop Tokens
</button>
</div>
</div>
);
}

export default Airdrop;
Loading