Skip to content

Commit b5b2973

Browse files
committed
add btcfun plugin
1 parent a00f723 commit b5b2973

File tree

9 files changed

+341
-0
lines changed

9 files changed

+341
-0
lines changed

packages/plugin-merlin/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# `@elizaos/plugin-merlin`
2+
3+
This plugin provides actions and providers for interacting with Merlin-compatible chains.
4+
5+
---
6+
7+
## Configuration
8+
9+
### Default Setup
10+
11+
By default, **Merlin mainnet** is enabled. To use it, simply add your private key to the `.env` file:
12+
13+
```env
14+
BTC_PRIVATE_KEY_WIF=your-private-key-here
15+
ADDRESS=your-address-here
16+
BTCFUN_API_URL=https://api-testnet-new.btc.fun
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import eslintGlobalConfig from "../../eslint.config.mjs";
2+
3+
export default [...eslintGlobalConfig];

packages/plugin-merlin/package.json

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@elizaos/plugin-btcfun",
3+
"version": "0.1.7-alpha.2",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@elizaos/core": "workspace:*",
9+
"@lifi/data-types": "5.15.5",
10+
"@lifi/sdk": "3.4.1",
11+
"@lifi/types": "16.3.0",
12+
"tsup": "8.3.5",
13+
"viem": "2.21.53"
14+
},
15+
"scripts": {
16+
"build": "tsup --format esm --dts",
17+
"dev": "tsup --format esm --dts --watch",
18+
"test": "vitest run",
19+
"lint": "eslint --fix --cache ."
20+
},
21+
"peerDependencies": {
22+
"whatwg-url": "7.1.0"
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { ByteArray, formatEther, parseEther, type Hex } from "viem";
2+
import {
3+
composeContext,
4+
generateObjectDeprecated,
5+
HandlerCallback,
6+
ModelClass,
7+
type IAgentRuntime,
8+
type Memory,
9+
type State,
10+
} from "@elizaos/core";
11+
12+
import { networks, Psbt } from 'bitcoinjs-lib';
13+
import { BIP32Factory } from 'bip32';
14+
import {randomBytes} from 'crypto';
15+
import * as ecc from 'tiny-secp256k1';
16+
import { BtcWallet, privateKeyFromWIF } from "@okxweb3/coin-bitcoin";
17+
import { base } from "@okxweb3/crypto-lib";
18+
import { mintTemplate } from "../templates";
19+
import {initBtcFunProvider} from "../providers/btcfun.ts";
20+
export { mintTemplate };
21+
22+
export const btcfunMintAction = {
23+
name: "btcfun",
24+
description: "btcfun mint brc20",
25+
handler: async (
26+
runtime: IAgentRuntime,
27+
_message: Memory,
28+
state: State,
29+
_options: any,
30+
callback?: HandlerCallback
31+
) => {
32+
console.log("btcfun action handler called");
33+
const btcfunProvider = initBtcFunProvider(runtime);
34+
35+
const chainCode = randomBytes(32);
36+
const bip32Factory = BIP32Factory(ecc);
37+
const network = networks.bitcoin;
38+
const privateKeyWif = runtime.getSetting("BTC_PRIVATE_KEY_WIF") ?? process.env.BTC_PRIVATE_KEY_WIF;
39+
let address = runtime.getSetting("ADDRESS") ?? process.env.ADDRESS;
40+
//const psbtHex = '70736274ff0100dd0200000002079a01841a17ba439545a00c73031db6a2317be7b12b619a4a1239a67e18ffaa0000000000fdffffffa0dffa35eee28e5c1b0c2048f5725f82200c6726337c27160b324e77a9c6ad530100000000fdffffff032202000000000000225120a11f9fa43193da4b9b825cf92d13fde040fc7205076c5a6eebfdb4b6a67a583d805c00000000000022512097162ff9c360ce05c59079a6f34897564528eee056dfcf2549383a3016683b8a4f0200000000000022512097162ff9c360ce05c59079a6f34897564528eee056dfcf2549383a3016683b8a000000000001012b220200000000000022512097162ff9c360ce05c59079a6f34897564528eee056dfcf2549383a3016683b8a011720e2df0c6fced9b8530a46649bc7ec06abfa8636a51bcacde4150c52715b76e9df0001012bc56800000000000022512097162ff9c360ce05c59079a6f34897564528eee056dfcf2549383a3016683b8a011720e2df0c6fced9b8530a46649bc7ec06abfa8636a51bcacde4150c52715b76e9df00000000';
41+
42+
const privateKey = base.fromHex(privateKeyFromWIF(privateKeyWif, network));
43+
const privateKeyHex = base.toHex(privateKey);
44+
console.log('Private key: ', privateKeyHex)
45+
const privateKeyBuffer = Buffer.from(privateKeyHex, 'hex');
46+
const keyPair = bip32Factory.fromPrivateKey(privateKeyBuffer, chainCode, network);
47+
const publicKeyBuffer = Buffer.from(keyPair.publicKey);
48+
const publicKeyHex = publicKeyBuffer.toString('hex');
49+
console.log('Public Key: ', publicKeyHex);
50+
51+
// Compose mint context
52+
const mintContext = composeContext({
53+
state,
54+
template: mintTemplate,
55+
});
56+
const content = await generateObjectDeprecated({
57+
runtime,
58+
context: mintContext,
59+
modelClass: ModelClass.LARGE,
60+
});
61+
let tick = content.inputToken;
62+
console.log("begin to mint token", tick, content)
63+
//todo remove
64+
tick = "dongj"
65+
66+
try {
67+
const {order_id, psbt_hex} = await btcfunProvider.createBrc20Order(publicKeyHex, address, publicKeyHex, address, 5, tick,"100",864000,"1000")
68+
console.log("11110",psbt_hex)
69+
const psbt = Psbt.fromHex(psbt_hex)
70+
let wallet = new BtcWallet()
71+
const toSignInputs = [];
72+
psbt.data.inputs.forEach((input, index)=>{
73+
toSignInputs.push({
74+
index: index,
75+
address: address,
76+
sighashTypes: [0],
77+
disableTweakSigner: false,
78+
});
79+
})
80+
81+
let params = {
82+
type: 3,
83+
psbt: psbt_hex,
84+
autoFinalized: false,
85+
toSignInputs: toSignInputs,
86+
};
87+
88+
let signParams = {
89+
privateKey: privateKeyWif,
90+
data: params,
91+
};
92+
console.log("signParams: ", signParams)
93+
//let signedPsbtHex = await wallet.signTransaction(signParams);
94+
95+
//todo open
96+
//await btcfunProvider.broadcastOrder(orderID, signedPsbtHex)
97+
//console.log('signedPsbtHex: ', signedPsbtHex, 'orderID: ', order_id)
98+
if (callback) {
99+
callback({
100+
text: `Successfully mint ${tick} tokens`,
101+
content: {
102+
success: true,
103+
orderID: order_id,
104+
//psbtHex: signedPsbtHex,
105+
},
106+
});
107+
}
108+
} catch (error) {
109+
console.error('Error:', error);
110+
}
111+
},
112+
template: mintTemplate,
113+
validate: async (runtime: IAgentRuntime) => {
114+
const privateKey = runtime.getSetting("BTC_PRIVATE_KEY_WIF");
115+
return typeof privateKey === "string" && privateKey.length > 0;
116+
},
117+
examples: [
118+
[
119+
{
120+
user: "assistant",
121+
content: {
122+
text: "I'll help you mint 100000000 Party",
123+
action: "MINT_BRC20",
124+
},
125+
},
126+
{
127+
user: "user",
128+
content: {
129+
text: "import token BRC20 `Party`",
130+
action: "MINT_BRC20",
131+
},
132+
},
133+
],
134+
],
135+
similes: ["MINT_BRC20","MINT_RUNES"],
136+
};

packages/plugin-merlin/src/index.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {btcfunMintAction} from "./actions/btcfun.ts";
2+
3+
export * from "./providers/btcfun";
4+
5+
import type { Plugin } from "@elizaos/core";
6+
7+
export const merlinPlugin: Plugin = {
8+
name: "merlin",
9+
description: "merlin plugin",
10+
providers: [],
11+
evaluators: [],
12+
services: [],
13+
actions: [btcfunMintAction],
14+
};
15+
16+
export default merlinPlugin;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import fetch from 'node-fetch';
2+
import type {IAgentRuntime} from "@elizaos/core";
3+
4+
export const initBtcFunProvider = (runtime: IAgentRuntime) => {
5+
6+
const btcfunApiURL = runtime.getSetting("BTCFUN_API_URL") ?? process.env.BTCFUN_API_URL
7+
if (!btcfunApiURL) {
8+
throw new Error("BTCFUN_API_URL is not set");
9+
}
10+
11+
return new BtcfunProvider(btcfunApiURL);
12+
};
13+
14+
export class BtcfunProvider {
15+
private apiUrl: string;
16+
17+
constructor(apiUrl: string) {
18+
this.apiUrl = apiUrl;
19+
}
20+
21+
async validateBrc20(address: string, ticker: string) {
22+
const response = await fetch(`${this.apiUrl}/api/v1/import/brc20_validate`, {
23+
method: 'POST',
24+
headers: {
25+
'Content-Type': 'application/json',
26+
},
27+
body: JSON.stringify({
28+
address: address,
29+
ticker: ticker,
30+
}),
31+
});
32+
33+
if (!response.ok) {
34+
throw new Error(`Error: ${response.statusText}`);
35+
}
36+
37+
return response.json();
38+
}
39+
40+
async createBrc20Order(paymentFromPubKey: string, paymentFrom: string, ordinalsFromPubKey: string, ordinalsFrom: string, feeRate: number, tick: string, addressFundraisingCap: string, mintDeadline: number, mintCap: string) {
41+
const response = await fetch(`${this.apiUrl}/api/v1/import/brc20_order`, {
42+
method: 'POST',
43+
headers: {
44+
'Content-Type': 'application/json',
45+
},
46+
body: JSON.stringify({
47+
payment_from_pub_key: paymentFromPubKey,
48+
payment_from: paymentFrom,
49+
ordinals_from_pub_key: ordinalsFromPubKey,
50+
ordinals_from: ordinalsFrom,
51+
fee_rate: feeRate,
52+
tick: tick,
53+
address_fundraising_cap: addressFundraisingCap,
54+
mint_deadline: mintDeadline,
55+
mint_cap: mintCap,
56+
}),
57+
});
58+
59+
if (!response.ok) {
60+
throw new Error(`Error: ${response.statusText}`);
61+
}
62+
63+
const result = await response.json();
64+
65+
if (result.code === "OK" && result.data) {
66+
const { order_id, psbt_hex } = result.data;
67+
return { order_id, psbt_hex };
68+
} else {
69+
console.log("Invalid response", result)
70+
throw new Error("Invalid response");
71+
}
72+
}
73+
74+
async broadcastOrder(orderId: string, signedPsbtHex: string) {
75+
const response = await fetch(`${this.apiUrl}/api/v1/import/broadcast`, {
76+
method: 'POST',
77+
headers: {
78+
'Content-Type': 'application/json',
79+
},
80+
body: JSON.stringify({
81+
order_id: orderId,
82+
signed_psbt_hex: signedPsbtHex,
83+
}),
84+
});
85+
86+
if (!response.ok) {
87+
throw new Error(`Error: ${response.statusText}`);
88+
}
89+
90+
return response.json();
91+
}
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const mintTemplate = `Given the recent messages and wallet information below:
2+
3+
{{recentMessages}}
4+
5+
{{walletInfo}}
6+
7+
Extract the following information about the requested token swap:
8+
- Input token symbol (the token being mint), eg: mint token abc
9+
10+
Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined:
11+
12+
\`\`\`json
13+
{
14+
"inputToken": string | null,
15+
}
16+
\`\`\`
17+
`;

packages/plugin-merlin/tsconfig.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"extends": "../core/tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "./src",
6+
"typeRoots": [
7+
"./node_modules/@types",
8+
"./src/types"
9+
],
10+
"declaration": true
11+
},
12+
"include": [
13+
"src"
14+
]
15+
}

packages/plugin-merlin/tsup.config.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { defineConfig } from "tsup";
2+
3+
export default defineConfig({
4+
entry: ["src/index.ts"],
5+
outDir: "dist",
6+
sourcemap: true,
7+
clean: true,
8+
format: ["esm"], // Ensure you're targeting CommonJS
9+
external: [
10+
"dotenv", // Externalize dotenv to prevent bundling
11+
"fs", // Externalize fs to use Node.js built-in module
12+
"path", // Externalize other built-ins if necessary
13+
"@reflink/reflink",
14+
"@node-llama-cpp",
15+
"https",
16+
"http",
17+
"agentkeepalive",
18+
"viem",
19+
"@lifi/sdk",
20+
"@okxweb3/crypto-lib",
21+
],
22+
});

0 commit comments

Comments
 (0)