-
-
Save xhliu/6c94d4fddbe8de6b0bf0c8db2baa98b3 to your computer and use it in GitHub Desktop.
TAALSigner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { connect } from '../store/portSlice'; | |
import store, { RootState } from '../store/store'; | |
// import { sendMessageAndWaitForResponse } from '../utils/globals'; | |
// src/utils.ts | |
// import { Store } from 'redux'; // Assuming you are using Redux for state management | |
interface Port { | |
postMessage: (message: any) => void; | |
onMessage: { | |
addListener: (listener: (response: any) => void) => void; | |
removeListener: (listener: (response: any) => void) => void; | |
}; | |
} | |
interface QueueItem { | |
request: () => Promise<any>; | |
resolve: (response: any) => void; | |
reject: (error: any) => void; | |
} | |
const requestQueue: QueueItem[] = []; | |
const processQueue = async () => { | |
if (requestQueue.length === 0) { | |
return; | |
} | |
const currentItem = requestQueue[0]; | |
try { | |
const response = await currentItem.request(); | |
currentItem.resolve(response); | |
} catch (error) { | |
currentItem.reject(error); | |
} finally { | |
requestQueue.shift(); | |
processQueue(); | |
} | |
}; | |
let currentRequestId = 0; | |
export function sendMessageAndWaitForResponse(port: Port, message: any): Promise<any> { | |
return new Promise((resolve, reject) => { | |
const requestId = currentRequestId++; | |
message.requestId = requestId; | |
const request = async () => { | |
return new Promise((resolve, reject) => { | |
// Send the message | |
port.postMessage(message); | |
// Set up the response listener | |
function listener(response: any) { | |
if (response.requestId !== requestId) { | |
// Ignore the response if it doesn't match the request ID | |
return; | |
} | |
// Remove the listener after receiving the response | |
port.onMessage.removeListener(listener); | |
// Resolve the Promise with the response | |
resolve(response); | |
} | |
port.onMessage.addListener(listener); | |
// Set up a timeout to reject the Promise if there's no response after a specified time | |
setTimeout(() => { | |
// Remove the listener if it's still active | |
port.onMessage.removeListener(listener); | |
// Reject the Promise with an error | |
reject(new Error('Timeout waiting for response')); | |
}, 10000); // 10 seconds timeout | |
}); | |
}; | |
requestQueue.push({ request, resolve, reject }); | |
if (requestQueue.length === 1) { | |
processQueue(); | |
} | |
}); | |
} | |
// export async function sendAction(store: Store, action: string, payload: any): Promise<any> { | |
export async function sendAction(action: string, payload?: any): Promise<any> { | |
const state: RootState = store.getState(); | |
const port: RootState['port']['value'] = state.port.value; | |
if(!port){ | |
console.error('Port is not defined'); | |
} else | |
try{ | |
const message = payload ? { action, payload } : { action }; | |
const response = await sendMessageAndWaitForResponse(port, message); | |
if (response.action === 'error') { | |
throw new Error(response.payload.reason); | |
} | |
return response; | |
} catch (error) { | |
if (error instanceof Error) { | |
console.error('Error:', error.message); | |
} else { | |
console.error('Error:', error); | |
} | |
} | |
} | |
export function isTAALChromeWalletConnected(): boolean { | |
const state: RootState = store.getState(); | |
const port: RootState['port']['value'] = state.port.value; | |
if(port === undefined) { | |
return false; | |
} else { | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; | |
// Define the initial state and its type | |
interface PortState { | |
value: chrome.runtime.Port | undefined; | |
} | |
const initialState: PortState = { | |
value: undefined, | |
}; | |
// Create the slice | |
const portSlice = createSlice({ | |
name: 'port', | |
initialState, | |
reducers: { | |
connect: (state) => { | |
state.value = chrome.runtime.connect( | |
process.env.REACT_APP_TAAL_EXTENSION_ID!, | |
{ name: process.env.REACT_APP_NAME! }, | |
); | |
}, | |
}, | |
}); | |
// Export the actions and reducer | |
export const { connect } = portSlice.actions; | |
export default portSlice.reducer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { configureStore } from '@reduxjs/toolkit'; | |
import portReducer from './portSlice'; | |
const store = configureStore({ | |
reducer: { | |
port: portReducer, | |
}, | |
}); | |
// Define the RootState type | |
export type RootState = ReturnType<typeof store.getState>; | |
export default store; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
bsv, | |
Signer, | |
SignTransactionOptions, | |
SignatureRequest, | |
SignatureResponse, | |
Provider, | |
} from 'scrypt-ts'; | |
import { parseAddresses } from 'scrypt-ts/dist/bsv/utils'; | |
import AddressOption = bsv.Address //Z:\Projects\VSCodium\auoz\node_modules\scrypt-ts\dist\bsv\types.d.ts | |
import { Transaction, PublicKey, Script } from 'bsv'; | |
import { isTAALChromeWalletConnected, sendAction } from '../utils/globals'; | |
interface InputInfo { | |
inputIndex: number; | |
satoshis: number; | |
scriptHex: string; | |
} | |
const DEFAULT_SIGHASH_TYPE = bsv.crypto.Signature.ALL; //Z:\Projects\VSCodium\auoz\node_modules\scrypt-ts\dist\bsv\signers\sensilet-signer.js | |
export class TAALSigner extends Signer { | |
override async isAuthenticated(): Promise<boolean>{ | |
const isTAALConnected: boolean = isTAALChromeWalletConnected(); | |
return isTAALConnected; | |
} | |
override async requestAuth(): Promise<{ | |
isAuthenticated: boolean; | |
error: string; | |
}> { | |
const isTAALConnected: boolean = isTAALChromeWalletConnected(); | |
const errorString: string = 'TAAL chrome wallet is not connected!'; | |
return { | |
isAuthenticated: isTAALConnected, | |
error: errorString | |
}; | |
} | |
override async connect(provider: Provider): Promise<this> { | |
// we should make sure TAAL chrome wallet is connected before we connect a provider. | |
const isTAALConnected: boolean = isTAALChromeWalletConnected(); | |
if(!isTAALConnected) { | |
Promise.reject(new Error('TAAL chrome wallet is not connected!')) | |
} | |
if(!provider.isConnected()) { | |
// connect the provider | |
await provider.connect(); | |
} | |
this.provider = provider; | |
return this; | |
} | |
override async getDefaultAddress(): Promise<bsv.Address> { | |
const response = await sendAction('getAddress'); | |
return bsv.Address.fromString(response.payload); | |
} | |
override async getDefaultPubKey(): Promise<PublicKey> { | |
// Real code start | |
const response = await sendAction('getRootPublicKey'); | |
const rootPublicKey = new bsv.PublicKey(response.payload); | |
// Real code end | |
// Test code start | |
/* const address = await this.getDefaultAddress(); | |
const rootPublicKey = await this.getPubKey(address); */ | |
// Test code end | |
// console.log("getDefaultPubKey().toString(): ", rootPublicKey.toString()); | |
return Promise.resolve(rootPublicKey); | |
} | |
override async getPubKey(address: AddressOption): Promise<PublicKey> { | |
const response = await sendAction('getPublicKey'); | |
const publicKey = new bsv.PublicKey(response.payload); | |
// console.log("getPubKey().toString(): ", publicKey.toString()); | |
return Promise.resolve(publicKey); | |
} | |
override async signRawTransaction(rawTxHex: string, options: SignTransactionOptions): Promise<string> { | |
// convert `rawTxHex` to a transation object | |
const sigReqsByInputIndex: Map<number, SignatureRequest> = (options?.sigRequests || []).reduce((m, sigReq) => { m.set(sigReq.inputIndex, sigReq); return m; }, new Map()); | |
const tx = new bsv.Transaction(rawTxHex); | |
tx.inputs.forEach((_, inputIndex) => { | |
const sigReq = sigReqsByInputIndex.get(inputIndex); | |
if (!sigReq) { | |
throw new Error(`\`SignatureRequest\` info should be provided for the input ${inputIndex} to call #signRawTransaction`) | |
} | |
const script = sigReq.scriptHex ? new bsv.Script(sigReq.scriptHex) : bsv.Script.buildPublicKeyHashOut(sigReq.address.toString()); | |
// set ref output of the input | |
tx.inputs[inputIndex].output = new bsv.Transaction.Output({ | |
script, | |
satoshis: sigReq.satoshis | |
}) | |
}); | |
const signedTx = await this.signTransaction(tx, options); | |
return signedTx.toString(); | |
} | |
public async getNetwork() { | |
const response = await sendAction('getNetwork'); | |
const network = response.payload; | |
console.log('network:', network); | |
return network; | |
} | |
updateInputsWithInfo(tx: Transaction, inputInfos: InputInfo[]): Transaction { | |
tx.inputs.forEach((input, index) => { | |
// Find the corresponding inputInfo based on the inputIndex | |
const inputInfo = inputInfos.find((info) => info.inputIndex === index); | |
if (inputInfo) { | |
// Update the input properties using the inputInfo data | |
input.output = new bsv.Transaction.Output({ | |
satoshis: inputInfo.satoshis, | |
script: Script.fromHex(inputInfo.scriptHex), | |
}); | |
} | |
}); | |
return tx; | |
} | |
override async signTransaction(tx: Transaction, options?: SignTransactionOptions): Promise<Transaction> { | |
// console.log("signTransaction() tx: ", tx); | |
// console.log("signTransaction() options: ", options); | |
const network = await this.getNetwork(); | |
const address = await this.getDefaultAddress(); | |
// console.log("signTransaction() address.toString(): ",address.toString()); | |
// Generate default `sigRequests` if not passed by user | |
const sigRequests: SignatureRequest[] = options?.sigRequests?.length ? options.sigRequests : | |
tx.inputs.map((input, inputIndex) => { | |
const useAddressToSign = options && options.address ? options.address : | |
input.output?.script.isPublicKeyHashOut() | |
? input.output.script.toAddress(network) | |
: address; | |
// : this._address; //this._address = scryptlib_1.bsv.Address.fromString(addr); in getConnectedTarget() | |
/* console.log("signTransaction() input.output?.script.isPublicKeyHashOut(): ",input.output?.script.isPublicKeyHashOut()); | |
console.log("signTransaction() input.output.script.toAddress(network).toString(): ",input.output?.script.toAddress(network).toString()); | |
console.log("signTransaction() address.toString(): ",address.toString()); | |
console.log("signTransaction() useAddressToSign.toString(): ",useAddressToSign.toString()); */ | |
return { | |
inputIndex, | |
satoshis: input.output?.satoshis ?? 0, //satoshis: input.output?.satoshis, | |
address: useAddressToSign, | |
scriptHex: input.output?.script?.toHex() ?? '', //scriptHex: input.output?.script?.toHex(), | |
sigHashType: DEFAULT_SIGHASH_TYPE, | |
} | |
}) | |
// console.log("signTransaction() sigRequests: ",sigRequests); | |
// Test code start | |
/* const rootPublicKey = await this.getDefaultPubKey(); | |
const addressFromRootPublicKey = bsv.Address.fromPublicKey(rootPublicKey, network); | |
console.log("signTransaction() rootPublicKey address.toString(): ",addressFromRootPublicKey.toString()); | |
const publicKey = await this.getPubKey(address); | |
const addressFromPublicKey = bsv.Address.fromPublicKey(publicKey, network); | |
console.log("signTransaction() publicKey address.toString(): ",addressFromPublicKey.toString()); | |
const isPublicKeyCorrect = tx.inputs.some(input => { | |
const inputScript = input.output?.script; | |
if (inputScript?.isPublicKeyHashOut()) { | |
const inputAddress = inputScript.toAddress(network); | |
console.log('signTransaction() inputAddress.toString(): ', inputAddress.toString()); | |
console.log('signTransaction() address.toString(): ', address.toString()); | |
return inputAddress.toString() === address.toString(); | |
} | |
return false; | |
}); | |
console.log('signTransaction() isPublicKeyCorrect: ', isPublicKeyCorrect); | |
const privateKeyWIF: string = process.env.REACT_APP_TAAL_PRIVATE_KEY_WIF as string; | |
const privateKey = bsv.PrivateKey.fromWIF(privateKeyWIF); | |
const addressFromPrivateKeyWIF = privateKey.toAddress(network).toString(); | |
console.log('signTransaction() addressFromPrivateKeyWIF:', addressFromPrivateKeyWIF); */ | |
// Test code end | |
const rawTxHex = tx.toString(); | |
const inputInfos = sigRequests.flatMap((sigReq) => { | |
const addresses = parseAddresses(sigReq.address, network); | |
return addresses.map(address => { | |
return { | |
txHex: rawTxHex, | |
inputIndex: sigReq.inputIndex, | |
scriptHex: sigReq.scriptHex || bsv.Script.buildPublicKeyHashOut(address).toHex(), | |
satoshis: sigReq.satoshis, | |
sigtype: sigReq.sigHashType || DEFAULT_SIGHASH_TYPE, | |
address: address.toString() | |
} | |
}); | |
}); | |
// console.log("signTransaction() inputInfos: ",inputInfos); | |
// Modify the transaction object using the inputInfos data | |
const updatedTx = this.updateInputsWithInfo(tx, inputInfos); | |
// console.log("signTransaction() updatedTx: ",updatedTx); | |
const response = await sendAction('signTx', updatedTx); | |
const signedTxHex = response.payload; | |
// console.log("signTransaction() signedTxHex: ",signedTxHex); | |
// Create a new Transaction object from the signed transaction hex | |
const signedTx = new bsv.Transaction(signedTxHex); | |
// console.log("signTransaction() signedTx: ",signedTx); | |
// Modify the transaction object using the inputInfos data | |
const updatedSignedTx = this.updateInputsWithInfo(signedTx, inputInfos); | |
console.log("signTransaction() updatedSignedTx.hash: ",updatedSignedTx.hash); | |
return updatedSignedTx; | |
} | |
/** | |
* Get signatures with api | |
* @param rawTxHex a transation raw hex | |
* @param sigRequests a `SignatureRequest` array for the some inputs of the transaction. | |
* @returns a `SignatureResponse` array | |
*/ | |
async getSignatures(rawTxHex: string, sigRequests: SignatureRequest[]): Promise<SignatureResponse[]> { | |
throw new Error(`Method ${this.constructor.name}#getSignatures not implemented.`); | |
} | |
override async signMessage(message: string, address?: AddressOption): Promise<string> { | |
if (address) { | |
throw new Error(`${this.constructor.name}#signMessge with \`address\` param is not supported!`); | |
} | |
const response = await sendAction('signMessage', message); | |
return response.payload; //TODO | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment