Skip to content

Commit 0080cf8

Browse files
committed
add new reclaim script for asigna
1 parent 4cc3a0c commit 0080cf8

File tree

5 files changed

+108
-35
lines changed

5 files changed

+108
-35
lines changed

src/comps/ConnectWallet.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ import { useAtomValue, useSetAtom } from "jotai";
1717
import { useNotifications } from "@/hooks/use-notifications";
1818
import { NotificationStatusType } from "./Notifications";
1919
import { useEffect, useState } from "react";
20-
import { getAddresses, getAddressesAsigna } from "@/util/wallet-utils/src/getAddress";
21-
import { useAsignaConnect } from '@asigna/btc-connect'
20+
import {
21+
getAddresses,
22+
getAddressesAsigna,
23+
} from "@/util/wallet-utils/src/getAddress";
24+
import { useAsignaConnect } from "@asigna/btc-connect";
2225

2326
const WALLET_PROVIDERS = [
2427
{
@@ -73,8 +76,9 @@ const ConnectWallet = ({ onClose }: ConnectWalletProps) => {
7376
break;
7477
case WalletProvider.XVERSE:
7578
addresses = await getAddressesXverse();
79+
break;
7680
case WalletProvider.ASIGNA:
77-
addresses = await getAddressesAsigna(asignaConnect);
81+
addresses = await getAddressesAsigna({ action: asignaConnect });
7882
}
7983
const isMainnetAddress =
8084
addresses.payment.address.startsWith("bc1") ||
@@ -165,13 +169,16 @@ const ConnectWallet = ({ onClose }: ConnectWalletProps) => {
165169
alt={provider.name}
166170
/>
167171
<p className="ml-4 text-black">
168-
{provider.walletProvider === WalletProvider.ASIGNA && !available
169-
? 'Open as an embedded app in Asigna'
170-
: <>
171-
{provider.name}{""}
172+
{provider.walletProvider === WalletProvider.ASIGNA &&
173+
!available ? (
174+
"Open as an embedded app in Asigna"
175+
) : (
176+
<>
177+
{provider.name}
178+
{""}
172179
{!available && " is not available click to install"}
173180
</>
174-
}
181+
)}
175182
</p>
176183
</div>
177184
{available ? (

src/comps/Deposit.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,25 @@ const DepositFlowConfirm = ({
281281
// user cannot continue if they're not connected
282282
const paymentAddress = walletInfo.addresses.payment!;
283283

284-
const reclaimPublicKey = paymentAddress.publicKey;
284+
let reclaimPublicKeys = [paymentAddress.publicKey];
285+
let signatureThreshold = 1;
286+
287+
if (walletInfo.selectedWallet === WalletProvider.ASIGNA) {
288+
const { threshold, users } = walletInfo.addresses.musig!;
289+
signatureThreshold = threshold;
290+
reclaimPublicKeys = users.map((user) => user.publicKey);
291+
}
285292

286293
// Parse lockTime from env variable
287294
const parsedLockTime = parseInt(lockTime || "144");
288295

289296
// Create the reclaim script and convert to Buffer
290297
const reclaimScript = Buffer.from(
291-
createReclaimScript(parsedLockTime, reclaimPublicKey),
298+
createReclaimScript(
299+
parsedLockTime,
300+
reclaimPublicKeys,
301+
signatureThreshold,
302+
),
292303
);
293304

294305
const reclaimScriptHex = uint8ArrayToHexString(reclaimScript);
@@ -306,7 +317,8 @@ const DepositFlowConfirm = ({
306317
maxFee,
307318
parsedLockTime,
308319
getBitcoinNetwork(config.WALLET_NETWORK),
309-
reclaimPublicKey,
320+
reclaimPublicKeys,
321+
signatureThreshold,
310322
);
311323

312324
let txId = "";

src/util/atoms.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { atom, createStore } from "jotai";
44
import { NotificationEventType } from "@/comps/Notifications";
55
import getSbtcBridgeConfig from "@/actions/get-sbtc-bridge-config";
66
import { atomWithStorage } from "jotai/utils";
7+
import { AsignaUser } from "./wallet-utils/src/getAddress";
78

89
export const store = createStore();
910

@@ -29,7 +30,7 @@ export const eventsAtom = atom<NotificationEventType[]>([]);
2930
export enum WalletProvider {
3031
LEATHER = "leather",
3132
XVERSE = "xverse",
32-
ASIGNA = 'asigna',
33+
ASIGNA = "asigna",
3334
}
3435

3536
type Address = {
@@ -44,12 +45,17 @@ export const walletInfoAtom = atomWithStorage<{
4445
payment: Address | null;
4546
taproot: Address | null;
4647
stacks: Address | null;
48+
musig: {
49+
users: AsignaUser[];
50+
threshold: number;
51+
} | null;
4752
};
4853
}>("walletInfoV3", {
4954
selectedWallet: null,
5055
addresses: {
5156
payment: null,
5257
taproot: null,
5358
stacks: null,
59+
musig: null,
5460
},
5561
});

src/util/depositRequest.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { Taptree } from "bitcoinjs-lib/src/types";
77
import * as bip341 from "bitcoinjs-lib/src/payments/bip341";
88

99
import ecc from "@bitcoinerlab/secp256k1";
10+
import { toXOnly } from "bitcoinjs-lib/src/psbt/bip371";
11+
import { bytesToHex, hexToBytes } from "@stacks/common";
1012

1113
bitcoin.initEccLib(ecc);
1214

@@ -57,27 +59,45 @@ export const createDepositScript = (
5759
]);
5860
};
5961

62+
6063
export const createReclaimScript = (
6164
lockTime: number,
62-
userPublicKey: string,
63-
): Uint8Array => {
65+
leafPubkeys: string[],
66+
threshold: number = 1,
67+
) => {
6468
const { script, opcodes } = bitcoin;
6569

66-
// Convert the user public key to a Uint8Array
67-
const pubkey = hexToUint8Array(userPublicKey);
68-
// remove the 0x04 prefix
69-
const schnorrPublicKey = pubkey.slice(1);
70+
if (leafPubkeys.length < 1) {
71+
throw new Error("Incorrect number of leaf public keys");
72+
}
73+
74+
if (threshold > leafPubkeys.length || threshold <= 0) {
75+
throw new Error("Incorrect threshold");
76+
}
77+
78+
let leafScriptAsm = [
79+
toXOnly(hexToBytes(leafPubkeys[0])),
80+
opcodes.OP_CHECKSIG,
81+
];
82+
83+
if (leafPubkeys.length > 1) {
84+
leafScriptAsm = [
85+
...leafScriptAsm,
86+
...leafPubkeys
87+
.slice(1)
88+
.flatMap((p) => [toXOnly(hexToBytes(p)), opcodes.OP_CHECKSIGADD]),
89+
script.number.encode(threshold),
90+
opcodes.OP_NUMEQUAL,
91+
];
92+
}
7093

71-
// Encode lockTime
7294
const lockTimeEncoded = script.number.encode(lockTime);
7395

74-
// Return the combined Uint8Array
7596
const buildScript = script.compile([
7697
lockTimeEncoded,
7798
opcodes.OP_CHECKSEQUENCEVERIFY,
7899
opcodes.OP_DROP,
79-
schnorrPublicKey,
80-
opcodes.OP_CHECKSIG,
100+
...leafScriptAsm,
81101
]);
82102

83103
return buildScript;
@@ -89,13 +109,14 @@ export const createDepositAddress = (
89109
maxFee: number,
90110
lockTime: number,
91111
network: bitcoin.networks.Network,
92-
reclaimPublicKey: string,
112+
reclaimPublicKeys: string[],
113+
threshold: number = 1,
93114
): string => {
94115
const internalPubkey = hexToUint8Array(signerPubKey);
95116

96117
// Create the reclaim script and convert to Buffer
97118
const reclaimScript = Buffer.from(
98-
createReclaimScript(lockTime, reclaimPublicKey),
119+
createReclaimScript(lockTime, reclaimPublicKeys, threshold),
99120
);
100121

101122
// Create the deposit script and convert to Buffer

src/util/wallet-utils/src/getAddress.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,34 @@ type Results = {
2020
address: string;
2121
publicKey: string;
2222
};
23+
musig: {
24+
users: AsignaUser[];
25+
threshold: number;
26+
} | null;
27+
};
28+
29+
export type AsignaUser = {
30+
_id: string;
31+
address: string;
32+
__v: number;
33+
publicKey: string;
34+
walletClass: string;
35+
walletType: string;
2336
};
2437

2538
export type getAddresses = (params?: {
2639
message?: string;
2740
network?: DefaultNetworkConfigurations;
41+
action?: () => Promise<any>;
2842
}) => Promise<Results>;
2943

3044
const getAddressByPurpose = (
31-
response: RpcSuccessResponse<"wallet_connect">["result"],
45+
response: RpcSuccessResponse<"getAddresses">["result"],
3246
purpose: AddressPurpose,
3347
) => response.addresses.find((item) => item.purpose === purpose);
3448

3549
export function getWalletAddresses(
36-
response: RpcSuccessResponse<"wallet_connect">["result"],
50+
response: RpcSuccessResponse<"getAddresses">["result"],
3751
) {
3852
const taproot = getAddressByPurpose(response, AddressPurpose.Ordinals);
3953
if (!taproot) {
@@ -61,6 +75,7 @@ export function getWalletAddresses(
6175
address: stacks.address,
6276
publicKey: stacks.publicKey,
6377
},
78+
musig: null,
6479
};
6580
}
6681

@@ -69,8 +84,12 @@ export function getWalletAddresses(
6984
* @description Get the address for the user
7085
*/
7186
export const getAddressesXverse: getAddresses = async (params) => {
72-
const response = await request("wallet_connect", {
73-
message: params?.message,
87+
const response = await request("getAddresses", {
88+
purposes: [
89+
AddressPurpose.Ordinals,
90+
AddressPurpose.Payment,
91+
AddressPurpose.Stacks,
92+
],
7493
});
7594

7695
if (response.status === "error") {
@@ -81,23 +100,30 @@ export const getAddressesXverse: getAddresses = async (params) => {
81100
return getWalletAddresses(result);
82101
};
83102

84-
export const getAddressesAsigna = async (action: () => Promise<any>) => {
85-
const response = await action();
103+
export const getAddressesAsigna: getAddresses = async (params) => {
104+
if (!params?.action) {
105+
throw new Error("Action is required");
106+
}
107+
const response = await params.action();
86108
return {
87109
taproot: {
88-
address: '',
89-
publicKey: '',
110+
address: "",
111+
publicKey: "",
90112
},
91113
payment: {
92114
address: response.address,
93115
publicKey: response.publicKey,
94116
},
95117
stacks: {
96-
address: '',
97-
publicKey: '',
118+
address: "",
119+
publicKey: "",
120+
},
121+
musig: {
122+
users: response.users,
123+
threshold: response.threshold,
98124
},
99125
};
100-
}
126+
};
101127

102128
const extractAddressByType = (
103129
addresses: Address[],
@@ -137,5 +163,6 @@ export const getAddressesLeather: getAddresses = async () => {
137163
payment,
138164
taproot,
139165
stacks,
166+
musig: null,
140167
};
141168
};

0 commit comments

Comments
 (0)