I'm trying to sign a Psbt transaction from bitcoinjs-lib following what I found here:
https://github.com/helperbit/helperbit-wallet/blob/master/app/components/dashboard.wallet/bitcoin.service/ledger.ts
I've checked that the compressed publicKey both from ledger, and the one from bitcoinjsLib returned the same value.
I could sign it with the bitcoinjs-lib ECPair, but when I tries to sign it using ledger, it is always invalid.
Can someone helps me point out where did I made a mistake?
These variables is already mentioned in the code below, but for clarity purpose:
- mnemonics:
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
- previousTx:
02000000000101869362410c61a69ab9390b2167d08219662196e869626e8b0350f1a8e4075efb0100000017160014ef3fdddccdb6b53e6dd1f5a97299a6ba2e1c11c3ffffffff0240420f000000000017a914f748afee815f78f97672be5a9840056d8ed77f4887df9de6050000000017a9142ff4aa6ffa987335c7bdba58ef4cbfecbe9e49938702473044022061a01bf0fbac4650a9b3d035b3d9282255a5c6040aa1d04fd9b6b52ed9f4d20a022064e8e2739ef532e6b2cb461321dd20f5a5d63cf34da3056c428475d42c9aff870121025fb5240daab4cee5fa097eef475f3f2e004f7be702c421b6607d8afea1affa9b00000000
- paths:
["0'/0/0"]
- redeemScript: (non-multisig segwit)
00144328adace54072cd069abf108f97cf80420b212b
This is my minimum reproducible code I've got.
/* tslint:disable */
// #ts-check
require('regenerator-runtime');
const bip39 = require('bip39');
const { default: Transport } = require('#ledgerhq/hw-transport-node-hid');
const { default: AppBtc } = require('#ledgerhq/hw-app-btc');
const bitcoin = require('bitcoinjs-lib');
const mnemonics = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const NETWORK = bitcoin.networks.regtest;
/**
* #param {string} pk
* #returns {string}
*/
function compressPublicKey(pk) {
const { publicKey } = bitcoin.ECPair.fromPublicKey(Buffer.from(pk, 'hex'));
return publicKey.toString('hex');
}
/** #returns {Promise<any>} */
async function appBtc() {
const transport = await Transport.create();
const btc = new AppBtc(transport);
return btc;
}
const signTransaction = async() => {
const ledger = await appBtc();
const paths = ["0'/0/0"];
const [ path ] = paths;
const previousTx = "02000000000101869362410c61a69ab9390b2167d08219662196e869626e8b0350f1a8e4075efb0100000017160014ef3fdddccdb6b53e6dd1f5a97299a6ba2e1c11c3ffffffff0240420f000000000017a914f748afee815f78f97672be5a9840056d8ed77f4887df9de6050000000017a9142ff4aa6ffa987335c7bdba58ef4cbfecbe9e49938702473044022061a01bf0fbac4650a9b3d035b3d9282255a5c6040aa1d04fd9b6b52ed9f4d20a022064e8e2739ef532e6b2cb461321dd20f5a5d63cf34da3056c428475d42c9aff870121025fb5240daab4cee5fa097eef475f3f2e004f7be702c421b6607d8afea1affa9b00000000"
const utxo = bitcoin.Transaction.fromHex(previousTx);
const segwit = utxo.hasWitnesses();
const txIndex = 0;
// ecpairs things.
const seed = await bip39.mnemonicToSeed(mnemonics);
const node = bitcoin.bip32.fromSeed(seed, NETWORK);
const ecPrivate = node.derivePath(path);
const ecPublic = bitcoin.ECPair.fromPublicKey(ecPrivate.publicKey, { network: NETWORK });
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: ecPublic.publicKey, network: NETWORK });
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: NETWORK });
const redeemScript = p2sh.redeem.output;
const fromLedger = await ledger.getWalletPublicKey(path, { format: 'p2sh' });
const ledgerPublicKey = compressPublicKey(fromLedger.publicKey);
const bitcoinJsPublicKey = ecPublic.publicKey.toString('hex');
console.log({ ledgerPublicKey, bitcoinJsPublicKey, address: p2sh.address, segwit, fromLedger, redeemScript: redeemScript.toString('hex') });
var tx1 = ledger.splitTransaction(previousTx, true);
const psbt = new bitcoin.Psbt({ network: NETWORK });
psbt.addInput({
hash: utxo.getId(),
index: txIndex,
nonWitnessUtxo: Buffer.from(previousTx, 'hex'),
redeemScript,
});
psbt.addOutput({
address: 'mgWUuj1J1N882jmqFxtDepEC73Rr22E9GU',
value: 5000,
});
psbt.setMaximumFeeRate(1000 * 1000 * 1000); // ignore maxFeeRate we're testnet anyway.
psbt.setVersion(2);
/** #type {string} */
// #ts-ignore
const newTx = psbt.__CACHE.__TX.toHex();
console.log({ newTx });
const splitNewTx = await ledger.splitTransaction(newTx, true);
const outputScriptHex = await ledger.serializeTransactionOutputs(splitNewTx).toString("hex");
const expectedOutscriptHex = '0188130000000000001976a9140ae1441568d0d293764a347b191025c51556cecd88ac';
// stolen from: https://github.com/LedgerHQ/ledgerjs/blob/master/packages/hw-app-btc/tests/Btc.test.js
console.log({ outputScriptHex, expectedOutscriptHex, eq: expectedOutscriptHex === outputScriptHex });
const inputs = [ [tx1, 0, p2sh.redeem.output.toString('hex') /** ??? */] ];
const ledgerSignatures = await ledger.signP2SHTransaction(
inputs,
paths,
outputScriptHex,
0, // lockTime,
undefined, // sigHashType = SIGHASH_ALL ???
utxo.hasWitnesses(),
2, // version??,
);
const signer = {
network: NETWORK,
publicKey: ecPrivate.publicKey,
/** #param {Buffer} $hash */
sign: ($hash) => {
const expectedSignature = ecPrivate.sign($hash); // just for comparison.
const [ ledgerSignature0 ] = ledgerSignatures;
const decodedLedgerSignature = bitcoin.script.signature.decode(Buffer.from(ledgerSignature0, 'hex'));
console.log({
$hash: $hash.toString('hex'),
expectedSignature: expectedSignature.toString('hex'),
actualSignature: decodedLedgerSignature.signature.toString('hex'),
});
// return signature;
return decodedLedgerSignature.signature;
},
};
psbt.signInput(0, signer);
const validated = psbt.validateSignaturesOfInput(0);
psbt.finalizeAllInputs();
const hex = psbt.extractTransaction().toHex();
console.log({ validated, hex });
};
if (process.argv[1] === __filename) {
signTransaction().catch(console.error)
}
Ooof, finally got it working.
My mistake was I was trying to sign a p2sh-p2ms, By following a reference on how to sign a p2sh-p2wsh-p2ms.
And, also, that missing last 2 bit (01), which I think represent SIGHASH_ALL caused an error when I try to decode the signature.
this is my finalized working example.
// #ts-check
require('regenerator-runtime');
const bip39 = require('bip39');
const { default: Transport } = require('#ledgerhq/hw-transport-node-hid');
const { default: AppBtc } = require('#ledgerhq/hw-app-btc');
const serializer = require('#ledgerhq/hw-app-btc/lib/serializeTransaction');
const bitcoin = require('bitcoinjs-lib');
const mnemonics = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const NETWORK = bitcoin.networks.regtest;
const DEFAULT_LOCK_TIME = 0;
const SIGHASH_ALL = 1;
const PATHS = ["m/49'/1'/0'/0/0", "m/49'/1'/0'/0/1"];
async function appBtc() {
const transport = await Transport.create();
const btc = new AppBtc(transport);
return btc;
}
/**
* #param {string} pk
* #returns {string}
*/
function compressPublicKey(pk) {
const {
publicKey
} = bitcoin.ECPair.fromPublicKey(Buffer.from(pk, 'hex'));
return publicKey.toString('hex');
}
/**
* #param {AppBtc} ledger
* #param {bitcoin.Transaction} tx
*/
function splitTransaction(ledger, tx) {
return ledger.splitTransaction(tx.toHex(), tx.hasWitnesses());
}
const signTransaction = async() => {
const seed = await bip39.mnemonicToSeed(mnemonics);
const node = bitcoin.bip32.fromSeed(seed, NETWORK);
const signers = PATHS.map((p) => node.derivePath(p));
const publicKeys = signers.map((s) => s.publicKey);
const p2ms = bitcoin.payments.p2ms({ pubkeys: publicKeys, network: NETWORK, m: 1 });
const p2shP2ms = bitcoin.payments.p2sh({ redeem: p2ms, network: NETWORK });
const previousTx = '02000000000101588e8fc89afea9adb79de2650f0cdba762f7d0880c29a1f20e7b468f97da9f850100000017160014345766130a8f8e83aef8621122ca14fff88e6d51ffffffff0240420f000000000017a914a0546d83e5f8876045d7025a230d87bf69db893287df9de6050000000017a9142ff4aa6ffa987335c7bdba58ef4cbfecbe9e49938702483045022100c654271a891af98e46ca4d82ede8cccb0503a430e50745f959274294c98030750220331b455fed13ff4286f6db699eca06aa0c1c37c45c9f3aed3a77a3b0187ff4ac0121037ebcf3cf122678b9dc89b339017c5b76bee9fedd068c7401f4a8eb1d7e841c3a00000000';
const utxo = bitcoin.Transaction.fromHex(previousTx);
const txIndex = 0;
const destination = p2shP2ms;
const redeemScript = destination.redeem.output;
// const witnessScript = destination.redeem.redeem.output;
const ledgerRedeemScript = redeemScript;
// use witness script if the outgoing transaction was from a p2sh-p2wsh-p2ms instead of p2sh-p2ms
const fee = 1000;
/** #type {number} */
// #ts-ignore
const amount = utxo.outs[txIndex].value;
const withdrawAmount = amount - fee;
const psbt = new bitcoin.Psbt({ network: NETWORK });
const version = 1;
psbt.addInput({
hash: utxo.getId(),
index: txIndex,
nonWitnessUtxo: utxo.toBuffer(),
redeemScript,
});
psbt.addOutput({
address: '2MsK2NdiVEPCjBMFWbjFvQ39mxWPMopp5vp',
value: withdrawAmount
});
psbt.setVersion(version);
/** #type {bitcoin.Transaction} */
// #ts-ignore
const newTx = psbt.__CACHE.__TX;
const ledger = await appBtc();
const inLedgerTx = splitTransaction(ledger, utxo);
const outLedgerTx = splitTransaction(ledger, newTx);
const outputScriptHex = await serializer.serializeTransactionOutputs(outLedgerTx).toString('hex');
/** #param {string} path */
const signer = (path) => {
const ecPrivate = node.derivePath(path);
// actually only publicKey is needed, albeit ledger give an uncompressed one.
// const { publicKey: uncompressedPublicKey } = await ledger.getWalletPublicKey(path);
// const publicKey = compressPublicKey(publicKey);
return {
network: NETWORK,
publicKey: ecPrivate.publicKey,
/** #param {Buffer} $hash */
sign: async ($hash) => {
const ledgerTxSignatures = await ledger.signP2SHTransaction({
inputs: [[inLedgerTx, txIndex, ledgerRedeemScript.toString('hex')]],
associatedKeysets: [ path ],
outputScriptHex,
lockTime: DEFAULT_LOCK_TIME,
segwit: newTx.hasWitnesses(),
transactionVersion: version,
sigHashType: SIGHASH_ALL,
});
const [ ledgerSignature ] = ledgerTxSignatures;
const expectedSignature = ecPrivate.sign($hash);
const finalSignature = (() => {
if (newTx.hasWitnesses()) {
return Buffer.from(ledgerSignature, 'hex');
};
return Buffer.concat([
ledgerSignature,
Buffer.from('01', 'hex'), // SIGHASH_ALL
]);
})();
console.log({
expectedSignature: expectedSignature.toString('hex'),
finalSignature: finalSignature.toString('hex'),
});
const { signature } = bitcoin.script.signature.decode(finalSignature);
return signature;
},
};
}
await psbt.signInputAsync(0, signer(PATHS[0]));
const validate = await psbt.validateSignaturesOfAllInputs();
await psbt.finalizeAllInputs();
const hex = psbt.extractTransaction().toHex();
console.log({ validate, hex });
};
if (process.argv[1] === __filename) {
signTransaction().catch(console.error)
}
My guess is you have a space in the string passed to toByteArray function. This function doesn't trim input. Also doesn't check if input's length is even.
Related
I'm trying to create a script to execute a swap on Uniswap V3.
The code below works perfectly well on the Goerli Testnet Network but NOT on Mainnet.
In order to make in run on mainnet:
i've changed the tokens addresses to match the mainnet ones and also changed the INFURA_URL to the mainnet url but I keep getting failed transactions..
const address0 = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
const address1 = '0x6B175474E89094C44Da98b954EedeAC495271d0F'
Seems like the error might come from the swapRouterAddress but not sure..
Moreover, when I execute the swap manually using Uniswap website, i can see that the swap was executed using Uniswap V3: Router 2, but even if I use this Router Address on my script the transaction keep failing..
Does anyone knows how to solve this ?
Thanks a lot !
const { ethers } = require('ethers')
const { abi: IUniswapV3PoolABI } = require('#uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json')
const { abi: SwapRouterABI} = require('#uniswap/v3-periphery/artifacts/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json')
const { abi: UniswapV3Factory } = require('#uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json')
const { getPoolImmutables, getPoolState } = require('./helpers')
const ERC20ABI = require('./abi.json')
require('dotenv').config()
const INFURA_URL_TESTNET = process.env.INFURA_URL_TESTNET
// Wallets
const WALLETS = [process.env.WALLET_ADDRESS_1];
var SECRET = new Object();
SECRET[process.env.WALLET_ADDRESS_1] = process.env.WALLET_SECRET_1;
// Provider
const provider = new ethers.providers.JsonRpcProvider(INFURA_URL_TESTNET) // Goerli
const swapRouterAddress = '0xE592427A0AEce92De3Edee1F18E0157C05861564'
// Wrapped Ether
const name0 = 'Wrapped Ether'
const symbol0 = 'WETH'
const decimals0 = 18
const address0 = '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6'
// Token info
const name1 = 'Token'
const decimals1 = 18
const address1 = '0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844' // DAI
// SWAP
const factoryAddress = '0x1F98431c8aD98523631AE4a59f267346ea31F984'
async function buy(WETH_amount) {
const factoryContract = new ethers.Contract(
factoryAddress,
UniswapV3Factory,
provider
)
const poolAddress = await factoryContract.getPool(address0, address1, 500)
for (const WALLET of WALLETS)
{
const poolContract = new ethers.Contract(
poolAddress,
IUniswapV3PoolABI,
provider
)
const immutables = await getPoolImmutables(poolContract)
const state = await getPoolState(poolContract)
var WALLET_SECRET = SECRET[WALLET];
const wallet = new ethers.Wallet(WALLET_SECRET)
const connectedWallet = wallet.connect(provider)
const swapRouterContract = new ethers.Contract(
swapRouterAddress,
SwapRouterABI,
provider
)
const inputAmount = WETH_amount
// .001 => 1 000 000 000 000 000
const amountIn = ethers.utils.parseUnits(
inputAmount.toString(),
decimals0
)
const params = {
tokenIn: immutables.token1,
tokenOut: immutables.token0,
fee: immutables.fee,
recipient: WALLET,
deadline: Math.floor(Date.now() / 1000) + (60 * 5),
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0,
}
const transaction = swapRouterContract.connect(connectedWallet).exactInputSingle(
params,
{
gasLimit: ethers.utils.hexlify(1000000)
}
).then(transaction => {
console.log(transaction)
})
}
}
// MAIN
buy(WETH_amount_=0.001)
Note that the token address for WETH is different on mainnet - your code has the address for WETH on Goerli. See https://docs.uniswap.org/protocol/reference/deployments for some token addresses
I'm currently working with the cardano serialization library to code crypto transactions in typescript. I'm referencing a wallet connector template that has example code for transactions but is all written in Javascript. So in translating it over I've received a number of syntax errors that have generally been easy to resolve until now. I have a number of ts(2532) errors (Object is possibly 'undefined' that are solved through '?' chaining, I know that's not my best solution but for now I'm trying to get it error free first. When I utilize a ? operator chain though I then get the property does not exist on type 'never' error in the following code
getNetworkId = async () => {
try {
const networkId = await this?.API?.getNetworkId()
this.setState({ networkId })
} catch (err) {
console.log(err)
} }
Below I've included the majority of the code down until that error
export default class App extends React.Component<any, any> {
API: undefined
protocolParams: {
linearFee: { minFeeA: string; minFeeB: string }
minUtxo: string
poolDeposit: string
keyDeposit: string
maxValSize: number
maxTxSize: number
priceMem: number
priceStep: number
coinsPerUtxoWord: string
}
constructor(props: any) {
super(props)
this.state = {
selectedTabId: '1',
whichWalletSelected: undefined,
walletFound: false,
walletIsEnabled: false,
walletName: undefined,
walletIcon: undefined,
walletAPIVersion: undefined,
wallets: [],
networkId: undefined,
Utxos: undefined,
CollatUtxos: undefined,
balance: undefined,
changeAddress: undefined,
rewardAddress: undefined,
usedAddress: undefined,
txBody: undefined,
txBodyCborHex_unsigned: '',
txBodyCborHex_signed: '',
submittedTxHash: '',
addressBech32SendADA:
'addr_test1qrt7j04dtk4hfjq036r2nfewt59q8zpa69ax88utyr6es2ar72l7vd6evxct69wcje5cs25ze4qeshejy828h30zkydsu4yrmm',
lovelaceToSend: 3000000,
assetNameHex: '4c494645',
assetPolicyIdHex:
'ae02017105527c6c0c9840397a39cc5ca39fabe5b9998ba70fda5f2f',
assetAmountToSend: 5,
addressScriptBech32:
'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8',
datumStr: '12345678',
plutusScriptCborHex: '4e4d01000033222220051200120011',
transactionIdLocked: '',
transactionIndxLocked: 0,
lovelaceLocked: 3000000,
manualFee: 900000,
}
/**
* When the wallet is connect it returns the connector which is
* written to this API variable and all the other operations
* run using this API object
*/
this.API = undefined
/**
* Protocol parameters
* #type {{
* keyDeposit: string,
* coinsPerUtxoWord: string,
* minUtxo: string,
* poolDeposit: string,
* maxTxSize: number,
* priceMem: number,
* maxValSize: number,
* linearFee: {minFeeB: string, minFeeA: string}, priceStep: number
* }}
*/
this.protocolParams = {
linearFee: {
minFeeA: '44',
minFeeB: '155381',
},
minUtxo: '34482',
poolDeposit: '500000000',
keyDeposit: '2000000',
maxValSize: 5000,
maxTxSize: 16384,
priceMem: 0.0577,
priceStep: 0.0000721,
coinsPerUtxoWord: '34482',
}
this.pollWallets = this.pollWallets.bind(this)
}
/**
* Poll the wallets it can read from the browser.
* Sometimes the html document loads before the browser initialized browser plugins (like Nami or Flint).
* So we try to poll the wallets 3 times (with 1 second in between each try).
*
* Note: CCVault and Eternl are the same wallet, Eternl is a rebrand of CCVault
* So both of these wallets as the Eternl injects itself twice to maintain
* backward compatibility
*
* #param count The current try count.
*/
pollWallets = (count = 0) => {
const wallets = []
for (const key in window.cardano) {
if (window.cardano[key].enable && wallets.indexOf(key) === -1) {
wallets.push(key)
}
}
if (wallets.length === 0 && count < 3) {
setTimeout(() => {
this.pollWallets(count + 1)
}, 1000)
return
}
this.setState(
{
wallets,
whichWalletSelected: wallets[0],
},
() => {
this.refreshData()
}
)
}
/**
* Handles the tab selection on the user form
* #param tabId
*/
handleTabId = (tabId: any) => this.setState({ selectedTabId: tabId })
/**
* Handles the radio buttons on the form that
* let the user choose which wallet to work with
* #param obj
*/
handleWalletSelect = (obj: { target: { value: any } }) => {
const whichWalletSelected = obj.target.value
this.setState({ whichWalletSelected }, () => {
this.refreshData()
})
}
/**
* Generate address from the plutus contract cborhex
*/
generateScriptAddress = () => {
// cborhex of the alwayssucceeds.plutus
// const cborhex = "4e4d01000033222220051200120011";
// const cbor = Buffer.from(cborhex, "hex");
// const blake2bhash = blake.blake2b(cbor, 0, 28);
const script = PlutusScript.from_bytes(
Buffer.from(this.state.plutusScriptCborHex, 'hex')
)
// const blake2bhash = blake.blake2b(script.to_bytes(), 0, 28);
const blake2bhash =
'67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656'
const scripthash = ScriptHash.from_bytes(Buffer.from(blake2bhash, 'hex'))
const cred = StakeCredential.from_scripthash(scripthash)
const networkId = NetworkInfo.testnet().network_id()
const baseAddr = EnterpriseAddress.new(networkId, cred)
const addr = baseAddr.to_address()
const addrBech32 = addr.to_bech32()
// hash of the address generated from script
console.log(Buffer.from(addr.to_bytes(), 'utf8').toString('hex'))
// hash of the address generated using cardano-cli
const ScriptAddress = Address.from_bech32(
'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8'
)
console.log(Buffer.from(ScriptAddress.to_bytes(), 'utf8').toString('hex'))
console.log(ScriptAddress.to_bech32())
console.log(addrBech32)
}
/**
* Checks if the wallet is running in the browser
* Does this for Nami, Eternl and Flint wallets
* #returns {boolean}
*/
checkIfWalletFound = () => {
const walletKey = this.state.whichWalletSelected
const walletFound = !!window?.cardano?.[walletKey]
this.setState({ walletFound })
return walletFound
}
/**
* Checks if a connection has been established with
* the wallet
* #returns {Promise<boolean>}
*/
checkIfWalletEnabled = async () => {
let walletIsEnabled = false
try {
const walletName = this.state.whichWalletSelected
walletIsEnabled = await window.cardano[walletName].isEnabled()
} catch (err) {
console.log(err)
}
this.setState({ walletIsEnabled })
return walletIsEnabled
}
/**
* Enables the wallet that was chosen by the user
* When this executes the user should get a window pop-up
* from the wallet asking to approve the connection
* of this app to the wallet
* #returns {Promise<boolean>}
*/
enableWallet = async () => {
const walletKey = this.state.whichWalletSelected
try {
this.API = await window.cardano[walletKey].enable()
} catch (err) {
console.log(err)
}
return this.checkIfWalletEnabled()
}
/**
* Get the API version used by the wallets
* writes the value to state
* #returns {*}
*/
getAPIVersion = () => {
const walletKey = this.state.whichWalletSelected
const walletAPIVersion = window?.cardano?.[walletKey].apiVersion
this.setState({ walletAPIVersion })
return walletAPIVersion
}
/**
* Get the name of the wallet (nami, eternl, flint)
* and store the name in the state
* #returns {*}
*/
getWalletName = () => {
const walletKey = this.state.whichWalletSelected
const walletName = window?.cardano?.[walletKey].name
this.setState({ walletName })
return walletName
}
/**
* Gets the Network ID to which the wallet is connected
* 0 = testnet
* 1 = mainnet
* Then writes either 0 or 1 to state
* #returns {Promise<void>}
*/
getNetworkId = async () => {
try {
const networkId = await this?.API?.getNetworkId()
this.setState({ networkId })
} catch (err) {
console.log(err)
}
}
/**
* Gets the UTXOs from the user's wallet and then
* stores in an object in the state
* #returns {Promise<void>}
*/
getUtxos = async () => {
let Utxos = []
try {
const rawUtxos = await this?.API?.getUtxos()
for (const rawUtxo of rawUtxos) {
const utxo = TransactionUnspentOutput.from_bytes(
Buffer.from(rawUtxo, 'hex')
)
const input = utxo.input()
const txid = Buffer.from(
input.transaction_id().to_bytes(),
'utf8'
).toString('hex')
const txindx = input.index()
const output = utxo.output()
const amount = output.amount().coin().to_str() // ADA amount in lovelace
const multiasset = output.amount().multiasset()
let multiAssetStr = ''
if (multiasset) {
const keys = multiasset.keys() // policy Ids of thee multiasset
const N = keys.len()
// console.log(`${N} Multiassets in the UTXO`)
for (let i = 0; i < N; i++) {
const policyId = keys.get(i)
const policyIdHex = Buffer.from(
policyId.to_bytes(),
'utf8'
).toString('hex')
// console.log(`policyId: ${policyIdHex}`)
const assets = multiasset.get(policyId)
const assetNames = assets.keys()
const K = assetNames.len()
// console.log(`${K} Assets in the Multiasset`)
for (let j = 0; j < K; j++) {
const assetName = assetNames.get(j)
const assetNameString = Buffer.from(
assetName.name(),
'utf8'
).toString()
const assetNameHex = Buffer.from(
assetName.name(),
'utf8'
).toString('hex')
const multiassetAmt = multiasset.get_asset(policyId, assetName)
multiAssetStr += `+ ${multiassetAmt.to_str()} + ${policyIdHex}.${assetNameHex} (${assetNameString})`
// console.log(assetNameString)
// console.log(`Asset Name: ${assetNameHex}`)
}
}
}
Trying to exchange USDC to ETH vith Uniswap and Ethers, but getting errors all the time.
async function swapUsdcToEth(amount, walledAddress) {
const usdc = await Fetcher.fetchTokenData(chainId, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48');
const eth = await Fetcher.fetchTokenData(chainId, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2');
const pair = await Fetcher.fetchPairData(usdc, eth);
const route = new Route([pair], usdc);
const amountIn = new TokenAmount(usdc, amount);
const trade = new Trade(route, amountIn, TradeType.EXACT_INPUT);
const slippageTolerance = new Percent('50', '10000');
const value = ethers.BigNumber.from(trade.inputAmount.raw.toString()).toHexString();
const amountOutMin = ethers.BigNumber.from(trade.minimumAmountOut(slippageTolerance).raw.toString()).toHexString();
const deadline = Math.floor(Date.now() / 1000) + 60 * 20;
const uniswapRouterV2Address = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D';
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/b9fkdmkdkdv4937b52ea9637cf1d1bd');
const signer = new ethers.Wallet(walledAddress, provider);
const uniswap = new ethers.Contract(
uniswapRouterV2Address,
['function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)'],
signer
);
try {
const tx = await uniswap.swapExactTokensForETH(value, amountOutMin, [walledAddress], walledAddress, deadline);
const receipt = await tx.wait();
console.log('transaction was mined in block', receipt.blockNumber);
} catch (e) {
console.log(e);
}
}
Receiving errors like that: ' Error: cannot estimate gas; transaction may fail or may require manual gas limit '. What I am doing wrong?
So it looks like possibly a few things. Have you approved your token to be spent by the router?
Also I'm not seeing any gas settings in there
here is a working version that I have modified to make work ( This is setup to test on a mainnet fork, however it's using live data (as the AlphaRouter is only for mainnet live so the live data and the fork data will change with time and cause trade errors, so reset fork before using)
to make main net only take out local provider and change all providers to main net provider.
USE WITH CAUTION, I'M NOT PERFECT AND GUARANTEE NOTHING : Check slippage and all variables
This is built As a TOKEN to TOKEN Exact_Input swap with WETH deposit of 1 ETH.
To use ETH you can remove Weth deposit and token in approval and use the BigNumber.from(typedValueParsed) as the value of the transaction instead of 0
As I don't know EtherJS to well the Gas Price and Gas Limit for the Deposit and approval is a flat 100 gwei & 300k Limit, and should be modified for current network gas price and estimated gas Limit.
import { AlphaRouter } from '#uniswap/smart-order-router'
import { Token, CurrencyAmount } from '#uniswap/sdk-core'
import { JSBI, Percent } from "#uniswap/sdk";
import { ethers, BigNumber } from "ethers";
const V3_SWAP_ROUTER_ADDRESS = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45";
const TokenInput = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const TokenOutput = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const web3Provider = new ethers.providers.JsonRpcProvider("https://eth-mainnet.alchemyapi.io/v2/");
const web3 = new ethers.providers.JsonRpcProvider("http://127.0.0.1:8545/");
const privateKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
const wallet = new ethers.Wallet(privateKey,web3);
const address = wallet.address;
import * as fs from 'fs';
let UniV3RouterAbi = fs.readFileSync('NewUniRouter.json');
const V3routerAbi = JSON.parse(UniV3RouterAbi);
let ERC20Abi = fs.readFileSync('ERC20.json');
const ERC20 = JSON.parse(ERC20Abi);
let WETHAbij = fs.readFileSync('WETHAbi.json');
const WETHAbi = JSON.parse(WETHAbij);
async function log(inpt){
console.log(inpt);
console.log("");
}
async function TokBal(tokens){
var ERC20contract = new ethers.Contract(tokens, ERC20, web3);
var myERC20bal = await ERC20contract.balanceOf(wallet.address);
return myERC20bal;
}
async function Deposit(amt){
var WethC = new ethers.Contract(TokenInput, WETHAbi, web3);
var datac = await WethC.populateTransaction["deposit"]();
var ncn = await wallet.getTransactionCount();
const transaction = {
data: datac.data,
nonce: ncn,
to: TokenInput,
value: BigNumber.from(amt),
from: wallet.address,
gasPrice: '0x174876e800',
gasLimit: '0x493e0',
};
const signedTx = await wallet.signTransaction(transaction);
const txHash = await web3.sendTransaction(signedTx);
log(txHash.hash);
}
async function Approve(Toked, amt){
var WethC = new ethers.Contract(Toked, ERC20, web3);
var datac = await WethC.populateTransaction["approve"](V3_SWAP_ROUTER_ADDRESS, amt);
var ncn = await wallet.getTransactionCount();
const transaction = {
data: datac.data,
nonce: ncn,
to: Toked,
value: BigNumber.from("0"),
from: wallet.address,
gasPrice: '0x174876e800',
gasLimit: '0x493e0',
};
const signedTx = await wallet.signTransaction(transaction);
const txHash = await web3.sendTransaction(signedTx);
log(txHash.hash);
var appFor = await WethC.callStatic.allowance(wallet.address, V3_SWAP_ROUTER_ADDRESS);
log("Approved : "+appFor.toString());
}
const router = new AlphaRouter({ chainId: 1, provider: web3Provider });
const WETH = new Token(
router.chainId,
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
18,
'WETH',
'Wrapped Ether'
);
const USDC = new Token(
router.chainId,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
6,
'USDC',
'USD//C'
);
const typedValueParsed = '1000000000000000000';
const wethAmount = CurrencyAmount.fromRawAmount(WETH, JSBI.BigInt(typedValueParsed));
const IO = "Exact_Input"
const TradeType = IO == "Exact_Input" ? 0 : 1;
const route = await router.route(
wethAmount,
USDC,
TradeType,
{
recipient: wallet.address,
slippageTolerance: new Percent(5, 100),
deadline: Math.floor(Date.now()/1000 +1800)
}
);
var Ebal = await web3.getBalance(wallet.address);
log("Wallet Balance : "+Ebal.toString());
var tbal = await TokBal(TokenOutput);
log("Token Out Balance : "+tbal.toString());
await Deposit("1000000000000000000");
await Approve(TokenInput,"1000000000000000000");
var tbalW = await TokBal(TokenInput);
log("Token In Balance : "+tbalW.toString());
log(`Quote Exact In: ${route.quote.toFixed(wethAmount.currency === WETH ? USDC.decimals : WETH.decimals)}`);
log(`Gas Adjusted Quote In: ${route.quoteGasAdjusted.toFixed(wethAmount.currency === WETH ? USDC.decimals : WETH.decimals)}`);
var nc = await wallet.getTransactionCount();
const transaction = {
data: route.methodParameters.calldata,
nonce: nc,
to: V3_SWAP_ROUTER_ADDRESS,
value: BigNumber.from(0),
from: wallet.address,
gasPrice: BigNumber.from(route.gasPriceWei),
gasLimit: BigNumber.from(route.estimatedGasUsed).add(BigNumber.from("50000")),
};
const signedTx = await wallet.signTransaction(transaction);
const PretxHash = ethers.utils.keccak256(signedTx);
const txHash = await web3.sendTransaction(signedTx)
log(txHash.hash);
var Ebal = await web3.getBalance(wallet.address);
log("Wallet Balance : "+Ebal.toString());
var tbal = await TokBal(TokenOutput);
log("Token Out Balance : "+tbal.toString());
var tbalW = await TokBal(TokenInput);
log("Token In Balance : "+tbalW.toString());
to get ETH use this in place of the output token
outPutAddress === WETH9[chainId].address ? nativeOnChain(chainId) : outPutToken,
EDIT>>>>>>>>>>
modified for new versions
import { ethers } from 'ethers'
import {AlphaRouter, ChainId, SwapType, nativeOnChain} from '#uniswap/smart-order-router'
import { TradeType, CurrencyAmount, Percent, Token } from '#uniswap/sdk-core'
import { TickMath } from '#uniswap/v3-sdk';
import JSBI from 'jsbi';
import bn from 'bignumber.js'
async function main() {
const MY_ADDRESS = "<ADDRESS>";
const web3Provider = new ethers.providers.JsonRpcProvider('https://eth-mainnet.alchemyapi.io/v2/<RPC_KEY>')
const router = new AlphaRouter({ chainId: 1, provider: web3Provider });
const WETH = new Token(
1,
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
18,
'WETH',
'Wrapped Ether'
);
const USDC = new Token(
ChainId.MAINNET,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
6,
'USDC',
'USD//C'
);
const AAVE = new Token(
ChainId.MAINNET,
'0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9',
18,
'AAVE',
'AAVE'
);
const options = {
recipient: MY_ADDRESS,
slippageTolerance: new Percent(10, 1000),
deadline: Math.floor(Date.now() / 1000 + 1800),
type: SwapType.SWAP_ROUTER_02,
}
const typedValueParsed = '10000000000000000000'
const route = await router.route(
CurrencyAmount.fromRawAmount(
AAVE,
typedValueParsed.toString()
),
nativeOnChain(ChainId.MAINNET),
TradeType.EXACT_INPUT,
options
)
console.log(`Quote Exact In: ${route.quote.toFixed(route.quote.currency.decimals)}`);
console.log(`Gas Adjusted Quote In: ${route.quoteGasAdjusted.toFixed(route.quote.currency.decimals)}`);
console.log(`Gas Used USD: ${route.estimatedGasUsedUSD.toFixed(2)}`);
console.log(route.methodParameters.calldata);
}
main()
I am having problem in my routing.I used Vanilla javascript in routing and it worked but when I go from http://127.0.0.1:5500/ to http://127.0.0.1:5500/about and then refresh about page it says Cannot GET/about
My javascript code - router.js file
const rootDiv = document.getElementById("root");
let home = "";
let about = "";
let contact = "";
let profile = "";
/**
* #param {String} page
* #returns {String}
*/
const loadPage = async (page) => {
const response = await fetch(page);
const resHtml = await response.text();
return resHtml;
};
const loadAllPages = async () => {
home = await loadPage("home.html");
about = await loadPage("about.html");
contact = await loadPage("contact.html");
profile = await loadPage("profile.html");
};
const main = async () => {
await loadAllPages();
rootDiv.innerHTML = home;
routes = {
"/": home,
"/contact": contact,
"/about": about,
"/profile": profile,
};
};
main();
/**
* #param {String} pathname
*/
const onNavigate = (pathname) => {
window.history.pushState({}, pathname, window.location.origin + pathname);
rootDiv.innerHTML = routes[pathname];
};
window.onpopstate = () => {
rootDiv.innerHTML = routes[window.location.pathname];
};
Why I am getting this error??Can anyone please get me out of this.
All HTML files are in same directory.
Any help here would be very appreciated!!
Thanks!!
Without using ThunderCore Hub or any Web3 wallet which supports Thunder Token, how could I programmatically send transactions or transfer Thunder Tokens?
To send a transaction on Thundercore, set these fields in the transaction:
set chainId with from net_version() (eth.net.getId())
set nonce with the value of eth_getTransactionCount() (eth.getTransactionCount)
set gas price from eth_gasPrice() (getGasPrice)
set gas limit from eth_estimateGas (eth.estimateGas)
See the submitTx method in the following code:
transfer.js
const fs = require('fs');
const path = require('path');
const Accounts = require('web3-eth-accounts');
const Eth = require('web3-eth');
const Web3 = require('web3');
const BN = Web3.utils.BN;
const pretty = require('./pretty');
const erc20Abi = require('./ERC20.abi.json');
const web3Provider = () => {
return Eth.giveProvider || 'https://mainnet-rpc.thundercore.com';
}
const signTx = async (fromAccount, tx) => {
const signedTx = await fromAccount.signTransaction(tx)
return signedTx.rawTransaction // hex string
}
class ChainHelper {
constructor(eth, chainId, fromAccount) {
this.eth = eth;
this.chainId = chainId;
this.fromAccount = fromAccount;
this.fromAddress = fromAccount.address;
}
async submitTx(toAddress, value, txData) {
const eth = this.eth;
const promiseResults = await Promise.all([
eth.getTransactionCount(this.fromAccount.address),
eth.getGasPrice(),
]);
const nonce = promiseResults[0];
const gasPrice = promiseResults[1];
const fromAddress = this.fromAddress;
const tx = {
'gasLimit': 0,
'chainId': this.chainId,
'gasPrice': gasPrice,
'nonce': nonce,
'from': fromAddress,
'to': toAddress,
'value': value,
'data': txData,
}
const gasMultiple = new BN(1.0);
tx.gasLimit = '0x' + (new BN(await eth.estimateGas(tx))).mul(gasMultiple).toString(16);
console.log('tx:', pretty.format(tx));
const rawTxStr = await signTx(this.fromAccount, tx);
return eth.sendSignedTransaction(rawTxStr);
}
async transferToken (contractAddress, toAddress, value) {
const eth = this.eth;
const contractAbi = erc20Abi;
const contract = new eth.Contract(contractAbi, contractAddress);
const txData = contract.methods.transfer(toAddress, value).encodeABI();
return this.submitTx(contractAddress, 0, txData);
}
}
const create = async (privateKey) => {
const accounts = new Accounts();
if (!privateKey.startsWith('0x')) {
privateKey = '0x' + privateKey;
}
const account = accounts.privateKeyToAccount(privateKey);
const eth = new Eth(web3Provider());
const networkId = await eth.net.getId();
return new ChainHelper(eth, networkId, account);
}
const readKeys = () => {
const privateKeys = fs.readFileSync(path.join(__dirname, '..', '.private-keys'),
{encoding: 'ascii'}).split('\n').filter(x => x.length > 0);
return privateKeys;
}
module.exports = {
create: create,
readKeys: readKeys,
};
testTransfer.js
const fs = require('fs');
const path = require('path');
const ChainHelper = require('../src/transfer.js');
const toAddress = '0x6f0d809e0fa6650460324f26df239bde6c004ecf';
describe('transfer', () => {
it('transfer TT', async() => {
const privateKey = ChainHelper.readKeys()[0]
const c = await ChainHelper.create(privateKey);
c.submitTx(toAddress, 1, '');
});
it('tokenTransfer', async() => {
const privateKey = ChainHelper.readKeys()[0]
const c = await ChainHelper.create(privateKey);
/* Token.issue() */
const jsonBuf = fs.readFileSync(path.join(__dirname, '..', 'build', 'contracts', 'Token.json'));
const contractData = JSON.parse(jsonBuf);
const contractAbi = contractData['abi'];
const contractAddress = contractData['networks'][c.chainId]['address'];
const contract = new c.eth.Contract(contractAbi, contractAddress);
const toAddress = c.fromAddress;
const tokenAmount = 2;
let txData = contract.methods.issue(tokenAmount, c.fromAddress).encodeABI();
let r = await c.submitTx(toAddress, 0, txData);
console.log('Token.issue receipt:', r);
/* Token.transfer() */
r = await c.transferToken(contractAddress, toAddress, tokenAmount)
console.log('Token.transfer receipt:', r);
});
});
The code above targets the command line or server-side node.js, to send transactions from a browser replace signTx and eth.sendSignedTransaction with web3.eth.sendTransaction
The complete example is in the transfer branch of this field-support repo.