Skip to content

Commit

Permalink
πŸ„β€β™‚οΈ FEATURE: Create using Metalet wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
riverrun46 committed Oct 18, 2023
1 parent 23d720d commit baf00f0
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 16 deletions.
18 changes: 11 additions & 7 deletions public/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ async function q(n) {
async function G(n) {
return await t("GetPublicKey", "query", n);
}
async function S() {
async function P() {
return await t("GetXPublicKey", "query");
}
async function w(n) {
return await t("GetBalance", "query", n);
}
async function P(n) {
async function S(n) {
return await t("GetUtxos", "query", n);
}
async function p(n) {
Expand All @@ -89,9 +89,12 @@ async function K(n) {
return await t("SignTransactions", "authorize", n);
}
async function k(n) {
return await t("Transfer", "authorize", n);
return await t("Pay", "authorize", n);
}
async function x(n) {
return await t("Transfer", "authorize", n);
}
async function N(n) {
return await t("Merge", "authorize", n);
}
async function g(n) {
Expand All @@ -116,14 +119,15 @@ const f = {
switchNetwork: b,
getAddress: q,
getPublicKey: G,
getXPublicKey: S,
getXPublicKey: P,
getBalance: w,
getUtxos: P,
transfer: k,
merge: x,
getUtxos: S,
transfer: x,
merge: N,
previewTransaction: E,
signTransaction: M,
signTransactions: K,
pay: k,
signMessage: A,
verifySignature: C,
eciesEncrypt: p,
Expand Down
3 changes: 3 additions & 0 deletions src/data/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const FEEB = 1
export const P2PKH_UNLOCK_SIZE = 1 + 1 + 72 + 1 + 33
export const DERIVE_MAX_DEPTH = 1000

export const NOTIFICATION_WIDTH = 360
export const NOTIFICATION_HEIGHT = 620

Expand Down
6 changes: 3 additions & 3 deletions src/lib/actions/pay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export async function process(params: any, host: string) {
const account = await getCurrentAccount()
const network = await getNetwork()

const signingTransactions = params.transactions
const signedTransactions = await payTransactions(account!, network, signingTransactions)
const toPayTransactions = params.transactions
const payedTransactions = await payTransactions(account!, network, toPayTransactions)

return { signedTransactions }
return { payedTransactions }
}
121 changes: 116 additions & 5 deletions src/lib/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { BN, mvc } from 'meta-contract'
import { BN, TxComposer, mvc } from 'meta-contract'
import { Buffer } from 'buffer'

import { getMvcRootPath, type Account } from './account'
import { parseLocalTransaction } from './metadata'
import { DERIVE_MAX_DEPTH, FEEB, P2PKH_UNLOCK_SIZE } from '@/data/config'
import { fetchUtxos } from '@/queries/utxos'

export function eciesEncrypt(message: string, privateKey: mvc.PrivateKey): string {
const publicKey = privateKey.toPublicKey()
Expand Down Expand Up @@ -268,21 +270,130 @@ type ToPayTransaction = {
export const payTransactions = async (
account: Account,
network: 'testnet' | 'mainnet',
toSignTransactions: ToPayTransaction[]
toPayTransactions: ToPayTransaction[]
) => {
// currently we only support one transaction
if (toSignTransactions.length !== 1) {
if (toPayTransactions.length !== 1) {
throw new Error('Currently we only support one transaction')
}

// first we finish the transaction by finding the appropriate utxo and calculating the change
const toSignTransaction = toSignTransactions[0]
const toSignTransaction = toPayTransactions[0]
const { txHex } = toSignTransaction
const tx = new mvc.Transaction(txHex)
const txComposer = new TxComposer(tx)
// make sure that every input has an output
const inputs = tx.inputs
for (let i = 0; i < inputs.length; i++) {
if (!inputs[i].output) {
throw new Error('The output of every input of the transaction must be provided')
}
}

const address = account.mvc.mainnetAddress
const addressObj = new mvc.Address(address, network)
// find out the total amount of the transaction (total output minus total input)
const totalAmount = tx.outputs.reduce((acc, output) => acc + output.satoshis, 0)
const totalOutput = tx.outputs.reduce((acc, output) => acc + output.satoshis, 0)
const totalInput = tx.inputs.reduce((acc, input) => acc + input.output!.satoshis, 0)
const difference = totalOutput - totalInput
const utxos = await fetchUtxos(address)
const pickedUtxos = pickUtxo(utxos, difference)

pickedUtxos.forEach((v) => {
txComposer.appendP2PKHInput({
address: addressObj,
txId: v.txId,
outputIndex: v.outputIndex,
satoshis: v.satoshis,
})
})
txComposer.appendP2PKHOutput({
address: addressObj,
satoshis: difference,
})
txComposer.appendChangeOutput(addressObj, FEEB)

// sign

const mneObj = mvc.Mnemonic.fromString(account.mnemonic)
const hdpk = mneObj.toHDPrivateKey('', network)

const rootPath = await getMvcRootPath()
const basePrivateKey = hdpk.deriveChild(rootPath)
const rootPrivateKey = hdpk.deriveChild(`${rootPath}/0/0`).privateKey

// now that we have root private key to sign other inputs
// we have to find out the private key of the 0-indexed input
const firstInputAddress = new mvc.Address(tx.inputs[0].output!.script, network)
let deriver = 0
let toUsePrivateKey: mvc.PrivateKey | undefined = undefined
while (deriver < DERIVE_MAX_DEPTH) {
const childPk = basePrivateKey.deriveChild(0).deriveChild(deriver)
const childAddress = childPk.publicKey.toAddress('mainnet' as any).toString()

if (childAddress === firstInputAddress.toString()) {
toUsePrivateKey = childPk.privateKey
break
}

deriver++
}
if (!toUsePrivateKey) {
throw new Error('Cannot find the private key of the first input')
}

// sign the first input with found private key
txComposer.unlockP2PKHInput(toUsePrivateKey, 0)

// txComposer.unlockP2PKHInput(firstInputPrivateKey, 0)

pickedUtxos.forEach((v, index) => {
txComposer.unlockP2PKHInput(rootPrivateKey, index)
})
}

type SA_utxo = {
txId: string
outputIndex: number
satoshis: number
address: string
height: number
}
function pickUtxo(utxos: SA_utxo[], amount: number) {
// amount + 2 outputs + buffer
let requiredAmount = amount + 34 * 2 * FEEB + 100
const candidateUtxos: SA_utxo[] = []
// split utxo to confirmed and unconfirmed and shuffle them
const confirmedUtxos = utxos
.filter((utxo) => {
return utxo.height > 0
})
.sort(() => Math.random() - 0.5)
const unconfirmedUtxos = utxos
.filter((utxo) => {
return utxo.height < 0
})
.sort(() => Math.random() - 0.5)

let current = 0
// use confirmed first
for (let utxo of confirmedUtxos) {
current += utxo.satoshis
// add input fee
requiredAmount += FEEB * P2PKH_UNLOCK_SIZE
candidateUtxos.push(utxo)
if (current > requiredAmount) {
return candidateUtxos
}
}
for (let utxo of unconfirmedUtxos) {
current += utxo.satoshis
// add input fee
requiredAmount += FEEB * P2PKH_UNLOCK_SIZE
candidateUtxos.push(utxo)
if (current > requiredAmount) {
return candidateUtxos
}
}
return candidateUtxos
}
1 change: 0 additions & 1 deletion src/queries/utxos.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { mvcApi } from './request'

export const fetchUtxos = async (address: string): Promise<any[]> => {
console.log('fetchUtxos', address)
const utxos: any = await mvcApi(`/address/${address}/utxo`).get()

return utxos
Expand Down

0 comments on commit baf00f0

Please sign in to comment.