Skip to content

Commit 1a2140f

Browse files
committed
Initial PR
1 parent bdf8920 commit 1a2140f

File tree

14 files changed

+4749
-0
lines changed

14 files changed

+4749
-0
lines changed

btc-p2sh/nodejs/.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
LIT_CAPACITY_CREDIT_TOKEN_ID=
2+
PKP_PUBLIC_KEY_1=
3+
PKP_PUBLIC_KEY_2=
4+
ETHEREUM_PRIVATE_KEY=
5+
LIT_NETWORK=
6+
BTC_DESTINATION_ADDRESS=

btc-p2sh/nodejs/.mocharc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://json.schemastore.org/mocharc.json",
3+
"require": "tsx"
4+
}

btc-p2sh/nodejs/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Bitcoin (P2SH) Transactions with Lit Protocol
2+
3+
These examples demonstrate how to use Lit Protocol PKPs (Programmable Key Pairs) to execute Bitcoin (P2SH) transactions.
4+
5+
## Prerequisites
6+
Before compiling and trying this example out, there are a few things you'll need to prepare.
7+
1. You will need to mint a PKP. The fastest way to do this is through the [Lit Explorer](https://explorer.litprotocol.com/). Please make sure you mint the PKP on the same network you run the example on.
8+
2. An Ethereum wallet. Please make sure that this wallet was used to mint the PKP, and therefore has ownership of it.
9+
3. The Bitcoin address derived from the PKP(s) must have a UTXO. Without a UTXO, the PKP will be unable to send any Bitcoin and this example will fail. Visit the [docs](https://developer.litprotocol.com/user-wallets/pkps/bitcoin/overview) for more information.
10+
4. Choose one of the four P2SH examples to run. This can be done at the bottom of the `executeBtcSigning` function in `src/index.ts`. Just uncomment the function you want to run.
11+
12+
## Running the Example
13+
14+
Once the prerequisites are met, you can run the code by running the following commands:
15+
16+
Install dependencies:
17+
```yarn```
18+
19+
Run the code:
20+
```yarn test```

btc-p2sh/nodejs/package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "btc-p2sh-example",
3+
"version": "0.1.0",
4+
"description": "Examples of P2SH transactions with Lit Protocol PKPs",
5+
"type": "module",
6+
"scripts": {
7+
"test": "npx @dotenvx/dotenvx run -- mocha test/**/*.spec.ts"
8+
},
9+
"dependencies": {
10+
"@lit-protocol/constants": "^6.6.0",
11+
"@lit-protocol/lit-node-client": "^6.6.0",
12+
"@mempool/mempool.js": "^2.3.0",
13+
"@simplewebauthn/browser": "^10.0.0",
14+
"@types/elliptic": "^6.4.18",
15+
"@types/express": "^4.17.21",
16+
"@types/node": "^22.5.5",
17+
"bip66": "^2.0.0",
18+
"bitcoinjs-lib": "^7.0.0-rc.0",
19+
"bn.js": "^5.2.1",
20+
"ecpair": "^3.0.0-rc.0",
21+
"elliptic": "^6.5.7",
22+
"node-fetch": "^3.3.2",
23+
"tiny-secp256k1": "^2.2.3"
24+
},
25+
"devDependencies": {
26+
"@types/chai": "^4.3.16",
27+
"@types/chai-json-schema": "^1.4.10",
28+
"@types/mocha": "^10.0.6",
29+
"chai": "^5.1.1",
30+
"chai-json-schema": "^1.5.1",
31+
"mocha": "^10.4.0",
32+
"tsc": "^2.0.4",
33+
"tsx": "^4.12.0",
34+
"typescript": "^5.5.3"
35+
}
36+
}

btc-p2sh/nodejs/src/index.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { LitNodeClient } from "@lit-protocol/lit-node-client";
2+
import { LitNetwork, LIT_RPC, } from "@lit-protocol/constants";
3+
import {
4+
createSiweMessageWithRecaps,
5+
generateAuthSig,
6+
LitAbility,
7+
LitActionResource,
8+
LitPKPResource,
9+
} from "@lit-protocol/auth-helpers";
10+
import { LitContracts } from "@lit-protocol/contracts-sdk";
11+
import { LIT_NETWORKS_KEYS } from "@lit-protocol/types";
12+
import * as ethers from "ethers";
13+
14+
import { getEnv } from "./utils/utils";
15+
import { singleSig } from "./p2sh/single-sig";
16+
import { multiSig } from "./p2sh/multi-sig";
17+
import { oneOfOneMultiSig } from "./p2sh/1of1-multi-sig";
18+
import { collaborativeMultiSig } from "./p2sh/collaborative";
19+
20+
const PKP_PUBLIC_KEY_1 = getEnv("PKP_PUBLIC_KEY_1");
21+
const PKP_PUBLIC_KEY_2 = getEnv("PKP_PUBLIC_KEY_2");
22+
const ETHEREUM_PRIVATE_KEY = getEnv("ETHEREUM_PRIVATE_KEY");
23+
const BTC_DESTINATION_ADDRESS = getEnv("BTC_DESTINATION_ADDRESS");
24+
const LIT_NETWORK = process.env["LIT_NETWORK"] as LIT_NETWORKS_KEYS || LitNetwork.Datil;
25+
const LIT_CAPACITY_CREDIT_TOKEN_ID = process.env["LIT_CAPACITY_CREDIT_TOKEN_ID"];
26+
27+
const ethersWallet = new ethers.Wallet(
28+
ETHEREUM_PRIVATE_KEY,
29+
new ethers.providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE)
30+
);
31+
32+
const address = ethersWallet.address;
33+
34+
export const executeBtcSigning = async () => {
35+
let litNodeClient: LitNodeClient;
36+
37+
try {
38+
litNodeClient = new LitNodeClient({
39+
litNetwork: LIT_NETWORK,
40+
debug: false,
41+
});
42+
await litNodeClient.connect();
43+
44+
const litContracts = new LitContracts({
45+
signer: ethersWallet,
46+
network: LIT_NETWORK,
47+
debug: false,
48+
});
49+
await litContracts.connect();
50+
51+
let capacityTokenId = LIT_CAPACITY_CREDIT_TOKEN_ID;
52+
if (!capacityTokenId) {
53+
console.log("🔄 No Capacity Credit provided, minting a new one...");
54+
const mintResult = await litContracts.mintCapacityCreditsNFT({
55+
requestsPerKilosecond: 10,
56+
daysUntilUTCMidnightExpiration: 1,
57+
});
58+
capacityTokenId = mintResult.capacityTokenIdStr;
59+
console.log(`✅ Minted new Capacity Credit with ID: ${capacityTokenId}`);
60+
} else {
61+
console.log(`ℹ️ Using provided Capacity Credit with ID: ${LIT_CAPACITY_CREDIT_TOKEN_ID}`);
62+
}
63+
64+
console.log("🔄 Creating capacityDelegationAuthSig...");
65+
const { capacityDelegationAuthSig } =
66+
await litNodeClient.createCapacityDelegationAuthSig({
67+
dAppOwnerWallet: ethersWallet,
68+
capacityTokenId,
69+
delegateeAddresses: [address],
70+
uses: "1",
71+
});
72+
console.log("✅ Capacity Delegation Auth Sig created");
73+
74+
console.log("🔄 Getting Session Signatures...");
75+
const sessionSigs = await litNodeClient.getSessionSigs({
76+
chain: "ethereum",
77+
expiration: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 24 hours
78+
capabilityAuthSigs: [capacityDelegationAuthSig],
79+
resourceAbilityRequests: [
80+
{
81+
resource: new LitPKPResource("*"),
82+
ability: LitAbility.PKPSigning,
83+
},
84+
{
85+
resource: new LitActionResource("*"),
86+
ability: LitAbility.LitActionExecution,
87+
},
88+
],
89+
authNeededCallback: async ({
90+
resourceAbilityRequests,
91+
expiration,
92+
uri,
93+
}) => {
94+
const toSign = await createSiweMessageWithRecaps({
95+
uri: uri!,
96+
expiration: expiration!,
97+
resources: resourceAbilityRequests!,
98+
walletAddress: address,
99+
nonce: await litNodeClient!.getLatestBlockhash(),
100+
litNodeClient,
101+
});
102+
103+
return await generateAuthSig({
104+
signer: ethersWallet,
105+
toSign,
106+
});
107+
},
108+
});
109+
console.log("✅ Got Session Signatures");
110+
111+
console.log("🔄 Executing Bitcoin Transaction...");
112+
return await singleSig(litNodeClient, sessionSigs, PKP_PUBLIC_KEY_1, BTC_DESTINATION_ADDRESS);
113+
114+
/**
115+
* Single Sig P2SH:
116+
return await singleSig(litNodeClient, sessionSigs, PKP_PUBLIC_KEY_1, BTC_DESTINATION_ADDRESS);
117+
118+
* Multi Sig P2SH:
119+
return await multiSig(litNodeClient, sessionSigs, PKP_PUBLIC_KEY_1, PKP_PUBLIC_KEY_2, BTC_DESTINATION_ADDRESS);
120+
121+
* 1of1 Multi Sig P2SH:
122+
return await oneOfOneMultiSig(litNodeClient, sessionSigs, PKP_PUBLIC_KEY_1, PKP_PUBLIC_KEY_2, BTC_DESTINATION_ADDRESS);
123+
124+
* Collaborative Multi Sig P2SH:
125+
return await collaborativeMultiSig(litNodeClient, sessionSigs, PKP_PUBLIC_KEY_1, PKP_PUBLIC_KEY_2, BTC_DESTINATION_ADDRESS);
126+
*/
127+
128+
} catch (error) {
129+
console.error(error);
130+
} finally {
131+
litNodeClient!.disconnect();
132+
}
133+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as bitcoin from "bitcoinjs-lib";
2+
import * as ecc from "tiny-secp256k1";
3+
import mempoolJS from "@mempool/mempool.js";
4+
import { LitNodeClient } from "@lit-protocol/lit-node-client";
5+
6+
import { convertSignature, broadcastTransaction } from "../utils/utils";
7+
import { litActionCode } from "../utils/litAction";
8+
9+
bitcoin.initEccLib(ecc);
10+
11+
export async function oneOfOneMultiSig(litNodeClient: LitNodeClient, sessionSigs: any, pkpPublicKey1: string, pkpPublicKey2: string, destinationAddress: string) {
12+
const network = bitcoin.networks.bitcoin;
13+
const pubKeyBuffer_1 = Buffer.from(pkpPublicKey1, "hex");
14+
const pubKeyBuffer_2 = Buffer.from(pkpPublicKey2, "hex");
15+
16+
const redeemScript = bitcoin.script.compile([
17+
bitcoin.opcodes.OP_1,
18+
pubKeyBuffer_1,
19+
pubKeyBuffer_2,
20+
bitcoin.opcodes.OP_2,
21+
bitcoin.opcodes.OP_CHECKMULTISIG,
22+
]);
23+
24+
const p2shPayment = bitcoin.payments.p2sh({
25+
redeem: { output: redeemScript },
26+
network: network,
27+
});
28+
29+
const { bitcoin: { addresses, transactions } } = mempoolJS({
30+
hostname: "mempool.space",
31+
network: "mainnet",
32+
});
33+
34+
const addressUtxos = await addresses.getAddressTxsUtxo({
35+
address: p2shPayment.address!,
36+
});
37+
console.log("P2SH Address:", p2shPayment.address);
38+
39+
if (addressUtxos.length === 0) {
40+
console.log("No UTXOs found for address:", p2shPayment.address);
41+
return;
42+
}
43+
44+
const utxo = addressUtxos[0];
45+
const psbt = new bitcoin.Psbt({ network });
46+
const utxoRawTx = await transactions.getTxHex({ txid: utxo.txid });
47+
48+
psbt.addInput({
49+
hash: utxo.txid,
50+
index: utxo.vout,
51+
nonWitnessUtxo: Buffer.from(utxoRawTx, "hex"),
52+
redeemScript: redeemScript,
53+
});
54+
55+
const fee = 1000;
56+
const amountToSend = utxo.value - fee;
57+
58+
psbt.addOutput({
59+
address: destinationAddress,
60+
value: BigInt(amountToSend),
61+
});
62+
63+
//@ts-ignore
64+
const tx = psbt.__CACHE.__TX.clone();
65+
const sighash = tx.hashForSignature(
66+
0,
67+
redeemScript,
68+
bitcoin.Transaction.SIGHASH_ALL
69+
);
70+
71+
const litActionResponse = await litNodeClient.executeJs({
72+
code: litActionCode,
73+
sessionSigs,
74+
jsParams: {
75+
publicKey: pkpPublicKey1,
76+
toSign: Buffer.from(sighash, "hex"),
77+
},
78+
});
79+
80+
const signatureWithHashType = await convertSignature(
81+
litActionResponse.signatures.btcSignature
82+
);
83+
84+
psbt.updateInput(0, {
85+
finalScriptSig: bitcoin.script.compile([
86+
bitcoin.opcodes.OP_0,
87+
signatureWithHashType,
88+
redeemScript,
89+
]),
90+
});
91+
92+
const txHex = psbt.extractTransaction().toHex();
93+
return await broadcastTransaction(txHex);
94+
}

0 commit comments

Comments
 (0)