Skip to content

Commit

Permalink
Add sign transactions example
Browse files Browse the repository at this point in the history
  • Loading branch information
aryzing committed Nov 21, 2024
1 parent c6b99d7 commit 9a005eb
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 21 deletions.
104 changes: 103 additions & 1 deletion example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -329,6 +330,7 @@ const StacksMethods = () => {
{stxAddressInfo?.[0]?.publicKey ? (
<SignTransaction network={network} publicKey={stxAddressInfo?.[0].publicKey} />
) : null}
<SignTransactions publicKey={stxAddressInfo[0].publicKey} />
</>
);
};
Expand Down
6 changes: 1 addition & 5 deletions example/src/components/bitcoin/GetAccounts.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand Down
6 changes: 1 addition & 5 deletions example/src/components/bitcoin/GetAddresses.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand Down
5 changes: 5 additions & 0 deletions example/src/components/common.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import styled from 'styled-components';

export const ErrorMessage = styled.div({
color: 'red',
});
81 changes: 81 additions & 0 deletions example/src/components/stacks/SignTransactions/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Card>
<h3>Sign transactions</h3>
<Stack>
<Checkbox
label="Pool Allow Contract"
checked={isPoolAllowContractSelected}
onChange={() => setIsPoolAllowContractSelected(!isPoolAllowContractSelected)}
/>
<Checkbox
label="Pool Delegate Stacks"
checked={isPoolDelegateStacksSelected}
onChange={() => setIsPoolDelegateStacks(!isPoolDelegateStacksSelected)}
/>
<Checkbox
label="Contract Deploy"
checked={isContractDeploySelected}
onChange={() => setIsContractDeploySelected(!isContractDeploySelected)}
/>
<Checkbox
label="Token Transfer"
checked={isTokenTransferSelected}
onChange={() => setIsTokenTransferSelected(!isTokenTransferSelected)}
/>
<Switch label="Broadcast" checked={broadcast} onChange={() => setBroadcast(!broadcast)} />

<Button onClick={handleSignTransactionsClick} disabled={signTransactionsMutation.isPending}>
{buttonText(signTransactionsMutation.isPending)}
</Button>

{signTransactionsMutation.isError && !signTransactionsMutation.isPending && (
<ErrorMessage>Failed to sign transactions. Check console for details.</ErrorMessage>
)}
</Stack>
</Card>
);
}
111 changes: 111 additions & 0 deletions example/src/components/stacks/SignTransactions/mutationFunction.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 9a005eb

Please sign in to comment.