Skip to content

Commit

Permalink
add btcfun plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Nevermore-Ray authored and Nevermore-Ray committed Jan 22, 2025
1 parent 256e663 commit 474ff46
Show file tree
Hide file tree
Showing 13 changed files with 1,161 additions and 11 deletions.
3 changes: 3 additions & 0 deletions characters/trump.character.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"clients": [],
"modelProvider": "openai",
"settings": {
"BTCFUN_API_URL": "https://api-testnet-new.btc.fun",
"BTC_PRIVATE_KEY_WIF": "KwdAdLkP4F1YxwmY1jabqUEcYCpVZ1j9MbmZsE1UiErysMWtcqVm",
"ADDRESS": "bc1pjutzl7wrvr8qt3vs0xn0xjyh2ezj3mhq2m0u7f2f8qarq9ng8w9qvm6g22",
"secrets": {},
"voice": {
"model": "en_US-male-medium"
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@
"@0glabs/0g-ts-sdk": "0.2.1",
"@coinbase/coinbase-sdk": "0.10.0",
"@deepgram/sdk": "^3.9.0",
"@okxweb3/coin-bitcoin": "1.2.0",
"@okxweb3/crypto-lib": "1.0.10",
"@vitest/eslint-plugin": "1.0.1",
"amqplib": "0.10.5",
"csv-parse": "5.6.0",
"ollama-ai-provider": "0.16.1",
"optional": "0.1.4",
"pnpm": "9.14.4",
"sharp": "0.33.5",
"tiny-secp256k1": "2.2.3",
"tslog": "4.9.3"
},
"packageManager": "[email protected]+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee",
Expand Down
16 changes: 16 additions & 0 deletions packages/plugin-btcfun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `@elizaos/plugin-btcfun`

This plugin provides actions and providers for interacting with bitcoin chains.

---

## Configuration

### Default Setup

By default, **Bitcoin mainnet** is enabled. To use it, simply add your private key to the `.env` file:

```env
BTC_PRIVATE_KEY_WIF=your-private-key-here
ADDRESS=your-address-here
BTCFUN_API_URL=https://api-testnet-new.btc.fun
3 changes: 3 additions & 0 deletions packages/plugin-btcfun/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import eslintGlobalConfig from "../../eslint.config.mjs";

export default [...eslintGlobalConfig];
24 changes: 24 additions & 0 deletions packages/plugin-btcfun/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@elizaos/plugin-btcfun",
"version": "0.1.7-alpha.2",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"@lifi/data-types": "5.15.5",
"@lifi/sdk": "3.4.1",
"@lifi/types": "16.3.0",
"tsup": "8.3.5",
"viem": "2.21.53"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"test": "vitest run",
"lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
135 changes: 135 additions & 0 deletions packages/plugin-btcfun/src/actions/btcfun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { ByteArray, formatEther, parseEther, type Hex } from "viem";
import {
composeContext,
generateObjectDeprecated,
HandlerCallback,
ModelClass,
type IAgentRuntime,
type Memory,
type State,
} from "@elizaos/core";

import { networks, Psbt } from 'bitcoinjs-lib';
import { BIP32Factory } from 'bip32';
import {randomBytes} from 'crypto';
import * as ecc from 'tiny-secp256k1';
import { BtcWallet, privateKeyFromWIF } from "@okxweb3/coin-bitcoin";
import { base } from "@okxweb3/crypto-lib";
import { mintTemplate } from "../templates";
import {initBtcFunProvider} from "../providers/btcfun.ts";
export { mintTemplate };

export const btcfunMintAction = {
name: "mint",
description: "btcfun mint brc20",
handler: async (
runtime: IAgentRuntime,
_message: Memory,
state: State,
_options: any,
callback?: HandlerCallback
) => {
console.log("btcfun action handler called");
const btcfunProvider = initBtcFunProvider(runtime);

const chainCode = randomBytes(32);
const bip32Factory = BIP32Factory(ecc);
const network = networks.bitcoin;
const privateKeyWif = runtime.getSetting("BTC_PRIVATE_KEY_WIF") ?? process.env.BTC_PRIVATE_KEY_WIF;
let address = runtime.getSetting("ADDRESS") ?? process.env.ADDRESS;

const privateKey = base.fromHex(privateKeyFromWIF(privateKeyWif, network));
const privateKeyHex = base.toHex(privateKey);
const privateKeyBuffer = Buffer.from(privateKeyHex, 'hex');
const keyPair = bip32Factory.fromPrivateKey(privateKeyBuffer, chainCode, network);
const publicKeyBuffer = Buffer.from(keyPair.publicKey);
const publicKeyHex = publicKeyBuffer.toString('hex');

// Compose mint context
const mintContext = composeContext({
state,
template: mintTemplate,
});
const content = await generateObjectDeprecated({
runtime,
context: mintContext,
modelClass: ModelClass.LARGE,
});
let tick = content.inputToken;
let mintcap = content.mintcap ?? runtime.getSetting("MINTCAP");
let mintdeadline = content.mintdeadline ?? runtime.getSetting("MINTDEADLINE");
let addressFundraisingCap = content.addressFundraisingCap ?? runtime.getSetting("ADDRESS_FUNDRAISING_CAP");
console.log("begin to mint token", tick, content)
//validateBrc20
await btcfunProvider.validateBrc20(address, tick);
console.log("validateBrc20 success")

try {
let {order_id, psbt_hex} = await btcfunProvider.createBrc20Order(
publicKeyHex, address, publicKeyHex, address, 10,
tick, addressFundraisingCap, mintdeadline, mintcap)
const psbt = Psbt.fromHex(psbt_hex)
let wallet = new BtcWallet()
const toSignInputs = [];
psbt.data.inputs.forEach((input, index)=>{
toSignInputs.push({
index: index,
address: address,
sighashTypes: [0],
disableTweakSigner: false,
});
})

let params = {
type: 3,
psbt: psbt_hex,
autoFinalized: false,
toSignInputs: toSignInputs,
};

let signParams = {
privateKey: privateKeyWif,
data: params,
};
let signedPsbtHex = await wallet.signTransaction(signParams);

const txHash = await btcfunProvider.broadcastOrder(order_id, signedPsbtHex)
console.log('signedPsbtHex: ', signedPsbtHex, 'orderID: ', order_id, 'txhash', txHash)
if (callback) {
callback({
text: `Successfully mint ${tick} tokens, mintcap ${mintcap}, mintdeadline ${mintdeadline}, addressFundraisingCap ${addressFundraisingCap} ,txhash ${txHash}`,
content: {
success: true,
orderID: order_id,
},
});
}
} catch (error) {
console.error('Error:', error);
}
},
template: mintTemplate,
validate: async (runtime: IAgentRuntime) => {
const privateKey = runtime.getSetting("BTC_PRIVATE_KEY_WIF");
return typeof privateKey === "string" && privateKey.length > 0;
},
examples: [
[
{
user: "assistant",
content: {
text: "I'll help you mint 100000000 Party",
action: "MINT_BRC20",
},
},
{
user: "user",
content: {
text: "import token BRC20 `Party`, mintcap 100000, addressFundraisingCap 10 mintdeadline 864000",
action: "MINT_BRC20",
},
},
],
],
similes: ["MINT_BRC20","MINT_RUNES"],
};
16 changes: 16 additions & 0 deletions packages/plugin-btcfun/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {btcfunMintAction} from "./actions/btcfun.ts";

export * from "./providers/btcfun";

import type { Plugin } from "@elizaos/core";

export const btcfunPlugin: Plugin = {
name: "btcfun",
description: "btcfun plugin",
providers: [],
evaluators: [],
services: [],
actions: [btcfunMintAction],
};

export default btcfunPlugin;
95 changes: 95 additions & 0 deletions packages/plugin-btcfun/src/providers/btcfun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import fetch from 'node-fetch';
import type {IAgentRuntime} from "@elizaos/core";

export const initBtcFunProvider = (runtime: IAgentRuntime) => {

const btcfunApiURL = runtime.getSetting("BTCFUN_API_URL") ?? process.env.BTCFUN_API_URL
if (!btcfunApiURL) {
throw new Error("BTCFUN_API_URL is not set");
}

return new BtcfunProvider(btcfunApiURL);
};

export class BtcfunProvider {
private apiUrl: string;

constructor(apiUrl: string) {
this.apiUrl = apiUrl;
}

async validateBrc20(address: string, ticker: string) {
const response = await fetch(`${this.apiUrl}/api/v1/import/brc20_validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
address: address,
ticker: ticker,
}),
});

if (!response.ok) {
throw new Error(`Error: ${response.statusText}`);
}

return response.json();
}

async createBrc20Order(paymentFromPubKey: string, paymentFrom: string, ordinalsFromPubKey: string, ordinalsFrom: string, feeRate: number, tick: string, addressFundraisingCap: string, mintDeadline: number, mintCap: string) {
const response = await fetch(`${this.apiUrl}/api/v1/import/brc20_order`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payment_from_pub_key: paymentFromPubKey,
payment_from: paymentFrom,
ordinals_from_pub_key: ordinalsFromPubKey,
ordinals_from: ordinalsFrom,
fee_rate: feeRate,
tick: tick,
address_fundraising_cap: addressFundraisingCap,
mint_deadline: mintDeadline,
mint_cap: mintCap,
}),
});

if (!response.ok) {
throw new Error(`Error: ${response.statusText}`);
}

const result = await response.json();

if (result.code === "OK" && result.data) {
const { order_id, psbt_hex } = result.data;
return { order_id, psbt_hex };
} else {
console.log("Invalid response", result)
throw new Error("Invalid response");
}
}

async broadcastOrder(orderId: string, signedPsbtHex: string) {
const response = await fetch(`${this.apiUrl}/api/v1/import/broadcast`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
order_id: orderId,
signed_psbt_hex: signedPsbtHex,
}),
});

if (!response.ok) {
throw new Error(`Error: ${response.statusText}`);
}
const result = await response.json();

if (result.code === "OK" && result.data) {
return result.data;
}
}
}
24 changes: 24 additions & 0 deletions packages/plugin-btcfun/src/templates/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const mintTemplate = `Given the recent messages and wallet information below:
{{recentMessages}}
{{walletInfo}}
Extract the following information about the requested token swap:
- Input token symbol (the token being mint), eg: mint token abc
- Input token mintcap eg: "10000"
- Input token addressFundraisingCap everyone can offer eg: "10"
- Input token mintdeadline ,duration Using seconds as the unit eg: 864000
Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined:
\`\`\`json
{
"inputToken": string | null,
"mintcap": string | 1000,
"addressFundraisingCap": string | 10,
"mintdeadline" : number | 864000,
}
\`\`\`
`;
15 changes: 15 additions & 0 deletions packages/plugin-btcfun/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "../core/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "./src",
"typeRoots": [
"./node_modules/@types",
"./src/types"
],
"declaration": true
},
"include": [
"src"
]
}
22 changes: 22 additions & 0 deletions packages/plugin-btcfun/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts"],
outDir: "dist",
sourcemap: true,
clean: true,
format: ["esm"], // Ensure you're targeting CommonJS
external: [
"dotenv", // Externalize dotenv to prevent bundling
"fs", // Externalize fs to use Node.js built-in module
"path", // Externalize other built-ins if necessary
"@reflink/reflink",
"@node-llama-cpp",
"https",
"http",
"agentkeepalive",
"viem",
"@lifi/sdk",
"@okxweb3/crypto-lib",
],
});
1 change: 1 addition & 0 deletions packages/plugin-pyth-data/schema.json

Large diffs are not rendered by default.

Loading

0 comments on commit 474ff46

Please sign in to comment.