Skip to content

Commit

Permalink
Merge pull request #338 from bob-collective/docs/sats-wagmi
Browse files Browse the repository at this point in the history
docs: update sats-wagmi
  • Loading branch information
gregdhill authored Sep 6, 2024
2 parents 4c1c127 + df15877 commit a53306b
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 74 deletions.
63 changes: 55 additions & 8 deletions docs/docs/build/bob-sdk/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,46 @@ const { uuid, psbtBase64 } = await gatewaySDK.startOrder(quote, quoteParams);

Create a Bitcoin transaction that sends the quoted `amount` of BTC to the LP's `bitcoinAddress`. This also publishes a hash of the order's parameters in the `OP_RETURN` of the transaction so the Gateway can trustlessly verify the order on BOB.

:::tip Connecting to Bitcoin wallets
We recommend using our [sats-wagmi](./sats-wagmi.md) package to interact with your user's Bitcoin wallet.
:::
<Tabs>
<TabItem value="sats-wagmi" label="sats-wagmi (Recommended)">

```ts
import { base64 } from "@scure/base";
import { Transaction } from "@scure/btc-signer";
Please follow the [guide here](./sats-wagmi.md) to install and use sats-wagmi. In this example, we sign the `psbtBase64` using sats-wagmi which abstracts the complex wallet logic for multiple connectors (including OKX, UniSat and Xverse).
It is also possible to directly use the `useSendGatewayTransaction` hook, example below.

```tsx
const { uuid, psbtBase64 } = await gatewaySDK.startOrder(quote, quoteParams);
const bitcoinTxHex = await connector.signAllInputs(psbtBase64!);
await gatewaySDK.finalizeOrder(uuid, bitcoinTxHex);
```

</TabItem>
<TabItem value="send-okx" label="Send (OKX)">

// SIGN THIS!
const tx = Transaction.fromPSBT(base64.decode(psbtBase64!));
Please refer to the [OKX docs](https://www.okx.com/web3/build/docs/sdks/chains/bitcoin/introduce) for more information.
In this example, instead of signing the `psbtBase64` we instead use the in-built wallet methods to directly send the BTC.

```ts
const { uuid, bitcoinAddress, satoshis, opReturnHash } = await gatewaySDK.startOrder(quote, quoteParams);
const { txhash } = await window.okxwallet.bitcoin.send({ from: quoteParams.fromUserAddress, to: bitcoinAddress, value: satoshis.toString(), memo: opReturnHash })
await gatewaySDK.finalizeOrder(uuid, txhash);
```

</TabItem>

<TabItem value="dynamic" label="Dynamic">

Please refer to the Dynamic guide on [PSBT signing](https://docs.dynamic.xyz/wallets/using-wallets/bitcoin/sign-a-psbt).

</TabItem>

<TabItem value="particle" label="Particle">

Please refer to the Particle guide on [BTC Connect](https://developers.particle.network/guides/integrations/partners/bob#connecting-bitcoin-wallets-to-bob-using-btc-connect).

</TabItem>

</Tabs>

### Finalize the Order

Submit the Bitcoin transaction as proof of transfer. This completes the process by transferring wrapped Bitcoin and ETH to the user's EVM address on BOB.
Expand All @@ -136,6 +164,25 @@ Get an array of pending and completed orders for a specific EVM address. Typical
const orders = await gatewaySDK.getOrders(userAddress);
```

### Example - sats-wagmi

<Tabs>
<TabItem value="sats-wagmi-app" label="Gateway.tsx">

```js reference title="Gateway.tsx"
https://github.com/bob-collective/sats-wagmi/blob/ae876d96bb2e54e5a24e0f3e1aaa6799565169e4/playgrounds/vite-react/src/Gateway.tsx#L1-L37
```

</TabItem>
<TabItem value="sats-wagmi-hook" label="useSendGatewayTransaction.tsx">

```js reference title="useSendGatewayTransaction.tsx"
https://github.com/bob-collective/sats-wagmi/blob/ae876d96bb2e54e5a24e0f3e1aaa6799565169e4/packages/sats-wagmi/src/hooks/useSendGatewayTransaction.tsx#L28-L69
```

</TabItem>
</Tabs>

## Conclusion

BOB Gateway enables staking, swapping, lending, and bridging Bitcoin with a single transaction. The BOB SDK makes it possible for you to bring Gateway and Bitcoin LSTs directly to your users.
Expand Down
147 changes: 87 additions & 60 deletions docs/docs/build/bob-sdk/sats-wagmi.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
sidebar_position: 4
---

# sats-wagmi Bitcoin Wallet React Hooks
# sats-wagmi - Reactive primitives for Bitcoin apps

sats-wagmi is a library with a handful of BTC wallet connectors, leaving aside the need of the developer to integrate each one individually. The library also exports useful React hooks that mimic the standard followed in the EVM [wagmi](https://wagmi.sh/react/getting-started) library.

Expand All @@ -13,104 +13,131 @@ sats-wagmi is a library with a handful of BTC wallet connectors, leaving aside t
- Unisat
- Leather
- Xverse
- Bitget (soon)
- Bitget
- OKX Wallet
- BTC functionality:
- send bitcoin
- inscribe (text and images)
- send inscription
- sign input (psbt)
- send BTC
- sign PSBTs
- React hooks

## Installation

To use sats-wagmi, all you need to do is install the
`@gobob/sats-wagmi`:
To use sats-wagmi, all you need to do is install `@gobob/sats-wagmi`:

```sh
# with Yarn
$ yarn add @gobob/sats-wagmi

# with npm
$ npm i @gobob/sats-wagmi

# with pnpm
$ pnpm add @gobob/sats-wagmi

# with Bun
$ bun add @gobob/sats-wagmi
```bash npm2yarn
npm install @gobob/sats-wagmi
```

## Usage
## Connect Wallet

### Connector
### 1. Wrap App in Context Provider

```ts
import { MMSnapConnector } from "./connectors";
To start, we will need to wrap our React App with Context so that our application is aware of sats-wagmi & React Query's reactive state and in-memory caching:

const mmSnap = new MMSnapConnector(network);
```tsx
// 1. Import modules
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { SatsWagmiConfig } from "@gobob/sats-wagmi";

// 2. Set up a React Query client.
const queryClient = new QueryClient()

mmSnap.connect();
function App() {
// 3. Wrap app with sats-wagmi and React Query context.
return (
<QueryClientProvider client={queryClient}>
<SatsWagmiConfig network="testnet" queryClient={queryClient}>
{/** ... */}
</SatsWagmiConfig>
</QueryClientProvider>
)
}
```

### React Hooks
### 2. Display Wallet Options

After that, we will create a `WalletOptions` component that will display our connectors. This will allow users to select a wallet and connect.

1. Wrap your application with the `SatsWagmiConfig` provided by **@gobob/sats-wagmi**.
Below, we are rendering a list of `connectors` retrieved from `useConnect`. When the user clicks on a connector, the `connect` function will connect the users' wallet.

```tsx
import { SatsWagmiConfig } from "@gobob/sats-wagmi";
import * as React from 'react'
import { useConnect, SatsConnector } from "@gobob/sats-wagmi";

export function WalletOptions() {
const { connectors, connect } = useConnect()

// Do this at the root of your application
function App({ children }) {
return <SatsWagmiConfig network="testnet">{children}</SatsWagmiConfig>;
return connectors.map((connector) => (
<button key={connector.name} onClick={() => connect({ connector })}>
{connector.name}
</button>
))
}
```

2. Now start by connecting:
### 3. Display Connected Account

```tsx
import { useConnect, SatsConnector } from "@gobob/sats-wagmi";
Lastly, if an account is connected, we want to show the connected address.

We are utilizing `useAccount` to extract the account and `useDisconnect` to show a "Disconnect" button so a user can disconnect their wallet.

function Example() {
const { connectors, connect } = useConnect();
```tsx
import { useAccount, useDisconnect } from "@gobob/sats-wagmi";

const handleConnect = (connector: SatsConnector) => {
connect({
connector,
});
};
function Account() {
const { address } = useAccount()
const { disconnect } = useDisconnect()

return (
<div>
{connectors.map((connector) => (
<button key={connector.name} onClick={() => handleConnect(connector)}>
{connector.name}
</button>
))}
<p>Address: {address}</p>
<button onClick={() => disconnect()}>Disconnect</button>
</div>
);
}
```

3. Once connected, you should be able to use the connector utility and have access to the connected BTC account:
## Send Transaction

Create your `SendTransaction` component that will contain the send transaction logic.

```tsx
import { useConnect, SatsConnector } from "@gobob/sats-wagmi";
import type { FormEvent } from 'react';
import { type Hex, parseUnits } from 'viem';
import { useSendTransaction } from "@gobob/sats-wagmi";

function SendTransaction() {
const { data: hash, error, isPending, sendTransaction } = useSendTransaction();

async function submit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const to = formData.get('address') as Hex;
const value = formData.get('value') as string;

function Example() {
const { address, connector } = useSatsAccount();
sendTransaction({ to, value: parseUnits(value, 8) });
}

const handleTransfer = () => {
connector?.sendToAddress(
"tb1p9gl248kp19jgennea98e2tv8acfrvfv0yws2tc5j6u72e84caapsh2hexs",
100000000
);
};
const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({
hash,
})

return (
<div>
<p>Address: {address}</p>
<button onClick={handleTransfer}>Transfer 1 BTC</button>
<h2>Send Transaction</h2>
<form onSubmit={submit}>
<input required name='address' placeholder='Address' />
<input required name='value' placeholder='Amount (BTC)' step='0.00000001' type='number' />
<button disabled={isPending} type='submit'>
{isPending ? 'Confirming...' : 'Send'}
</button>
</form>
{hash && <div>Transaction Hash: {hash}</div>}
{isConfirming && 'Waiting for confirmation...'}
{isConfirmed && 'Transaction confirmed.'}
{error && <div>Error: {error.message}</div>}
</div>
);
}
```
```
8 changes: 8 additions & 0 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ const config = {
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
codeblock: {
showGithubLink: true,
githubLinkLabel: 'View on GitHub',
showRunmeLink: false,
runmeLinkLabel: 'Checkout via Runme'
},

image: "img/bob-social-card.png",
metadata: [
{
Expand Down Expand Up @@ -210,6 +217,7 @@ const config = {
language: ["en"],
},
],
'docusaurus-theme-github-codeblock',
],
scripts: [
{
Expand Down
3 changes: 2 additions & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@scure/btc-signer": "^1.3.2",
"bitcoin-address-validation": "^2.2.3",
"clsx": "^2.0.0",
"docusaurus-theme-github-codeblock": "^2.0.2",
"prism-react-renderer": "^2.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
Expand Down Expand Up @@ -56,4 +57,4 @@
"engines": {
"node": ">=16.14"
}
}
}
6 changes: 3 additions & 3 deletions sdk/src/esplora.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ export interface Transaction {
}>
status: {
confirmed: boolean
block_height: number
block_hash: string
block_time: number
block_height?: number
block_hash?: string
block_time?: number
}
}

Expand Down
28 changes: 26 additions & 2 deletions sdk/src/gateway/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
} from "./types";
import { SYMBOL_LOOKUP, ADDRESS_LOOKUP } from "./tokens";
import { createBitcoinPsbt } from "../wallet";
import { Network } from "bitcoin-address-validation";
import { EsploraClient } from "../esplora";

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;

Expand Down Expand Up @@ -148,6 +150,10 @@ export class GatewayApiClient {
gatewayQuote: GatewayQuote,
params: Optional<GatewayQuoteParams, "toToken" | "amount">,
): Promise<GatewayStartOrder> {
if (!params.toUserAddress.startsWith("0x") || !ethers.isAddress(params.toUserAddress)) {
throw new Error("Invalid user address");
}

const request: GatewayCreateOrderRequest = {
gatewayAddress: gatewayQuote.gatewayAddress,
strategyAddress: gatewayQuote.strategyAddress,
Expand Down Expand Up @@ -209,10 +215,20 @@ export class GatewayApiClient {
* broadcast the transaction. This is step 2 of 2, see the {@link startOrder} method.
*
* @param uuid The id given by the {@link startOrder} method.
* @param bitcoinTxHex The hex encoded Bitcoin transaction.
* @param bitcoinTxOrId The hex encoded Bitcoin transaction or txid.
* @returns {Promise<string>} The Bitcoin txid.
*/
async finalizeOrder(uuid: string, bitcoinTxHex: string): Promise<string> {
async finalizeOrder(uuid: string, bitcoinTxOrId: string): Promise<string> {
bitcoinTxOrId = stripHexPrefix(bitcoinTxOrId);

let bitcoinTxHex: string;
if (bitcoinTxOrId.length === 64) {
const esploraClient = new EsploraClient(this.chain === Chain.BOB ? Network.mainnet : Network.testnet);
bitcoinTxHex = await esploraClient.getTransactionHex(bitcoinTxOrId);
} else {
bitcoinTxHex = bitcoinTxOrId;
}

const response = await fetch(`${this.baseUrl}/order/${uuid}`, {
method: "PATCH",
headers: {
Expand Down Expand Up @@ -354,3 +370,11 @@ function calculateOpReturnHash(req: GatewayCreateOrderRequest) {
),
);
}

function isHexPrefixed(str: string): boolean {
return str.slice(0, 2) === "0x";
}

function stripHexPrefix(str: string): string {
return isHexPrefixed(str) ? str.slice(2) : str;
}
Loading

0 comments on commit a53306b

Please sign in to comment.