Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merlin dev #2579

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/plugin-merlin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `@elizaos/plugin-merlin`

This plugin provides actions and providers for interacting with Merlin-compatible chains.

---

## Configuration

### Default Setup

By default, **Merlin 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-merlin/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-merlin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@elizaos/plugin-merlin",
"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"
}
}
136 changes: 136 additions & 0 deletions packages/plugin-merlin/src/actions/btcfun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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: "btcfun",
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 psbtHex = '70736274ff0100dd0200000002079a01841a17ba439545a00c73031db6a2317be7b12b619a4a1239a67e18ffaa0000000000fdffffffa0dffa35eee28e5c1b0c2048f5725f82200c6726337c27160b324e77a9c6ad530100000000fdffffff032202000000000000225120a11f9fa43193da4b9b825cf92d13fde040fc7205076c5a6eebfdb4b6a67a583d805c00000000000022512097162ff9c360ce05c59079a6f34897564528eee056dfcf2549383a3016683b8a4f0200000000000022512097162ff9c360ce05c59079a6f34897564528eee056dfcf2549383a3016683b8a000000000001012b220200000000000022512097162ff9c360ce05c59079a6f34897564528eee056dfcf2549383a3016683b8a011720e2df0c6fced9b8530a46649bc7ec06abfa8636a51bcacde4150c52715b76e9df0001012bc56800000000000022512097162ff9c360ce05c59079a6f34897564528eee056dfcf2549383a3016683b8a011720e2df0c6fced9b8530a46649bc7ec06abfa8636a51bcacde4150c52715b76e9df00000000';

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

// Compose mint context
const mintContext = composeContext({
state,
template: mintTemplate,
});
const content = await generateObjectDeprecated({
runtime,
context: mintContext,
modelClass: ModelClass.LARGE,
});
let tick = content.inputToken;
console.log("begin to mint token", tick, content)
//todo remove
tick = "dongj"

try {
const {order_id, psbt_hex} = await btcfunProvider.createBrc20Order(publicKeyHex, address, publicKeyHex, address, 5, tick,"100",864000,"1000")
console.log("11110",psbt_hex)
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,
};
console.log("signParams: ", signParams)
//let signedPsbtHex = await wallet.signTransaction(signParams);

//todo open
//await btcfunProvider.broadcastOrder(orderID, signedPsbtHex)
//console.log('signedPsbtHex: ', signedPsbtHex, 'orderID: ', order_id)
if (callback) {
callback({
text: `Successfully mint ${tick} tokens`,
content: {
success: true,
orderID: order_id,
//psbtHex: signedPsbtHex,
},
});
}
} 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`",
action: "MINT_BRC20",
},
},
],
],
similes: ["MINT_BRC20","MINT_RUNES"],
};
16 changes: 16 additions & 0 deletions packages/plugin-merlin/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 merlinPlugin: Plugin = {
name: "merlin",
description: "merlin plugin",
providers: [],
evaluators: [],
services: [],
actions: [btcfunMintAction],
};

export default merlinPlugin;
92 changes: 92 additions & 0 deletions packages/plugin-merlin/src/providers/btcfun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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}`);
}

return response.json();
}
}
17 changes: 17 additions & 0 deletions packages/plugin-merlin/src/templates/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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

Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined:

\`\`\`json
{
"inputToken": string | null,
}
\`\`\`
`;
15 changes: 15 additions & 0 deletions packages/plugin-merlin/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-merlin/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",
],
});
Loading