Calling a smart contract function using metamask with ether.js - javascript

I'm completely new to both blockchain and JavaScript.
I'm trying to create a simple web page where people could generate a "wedding" smart contract that basically store their 2 names. For this I have created a WeddingCerficate contract which store the names and have a getter function, and a WeddingCertificateFactory That enable me to generate a WeddingCertificate. You can find the code of the smart contracts in solidity below.
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract WeddingCertificate{
string private spouse1;
string private spouse2;
constructor(string memory _spouse1, string memory _spouse2) {
spouse1 = _spouse1;
spouse2 = _spouse2;
}
function getSpouses() public view returns (string memory,string memory) {
return (spouse1,spouse2);
}
}
contract WeddingCertificateFactory{
event Wedding(string _spouse1, string _spouse2, address indexed contract_adress );
function Unite(string memory _spouse1, string memory _spouse2)public returns (bool success) {
WeddingCertificate wedding = new WeddingCertificate(_spouse1, _spouse2);
emit Wedding(_spouse1,_spouse2 , address(wedding));
return true ;
}
}
I deployed the WeddingCertificateFactory on Goerli Tesnet. And now I'm trying to make a function in javascript (using ether.js) to enable a user to create his own weddingCertificate directly on a web interface.
For this I wrote the function below but for some reasons this only generates the new Wedding certificate once out 20. And even when it actually work, the two last print aren't visible in the console.
I do not get any error (at least that I can see in the console) when I test the function and nothing happen.
I'm not familiar with async in JavaScript, I also tried the .then( syntax but I didn't notice any difference.
async function CreateWedding(){
const spouse1 = document.getElementById("spouse1").value;
const spouse2 = document.getElementById("spouse2").value;
if (spouse1.length > 0 && spouse2.length >0) {
console.log(`spouse 1: ${spouse1} , spouse2 : ${spouse2} `);
const ethereum = window.ethereum ;
const accounts = await ethereum.request({
method: "eth_requestAccounts",
});
const provider = new ethers.providers.Web3Provider(ethereum, "any");
const walletAddress = accounts[0];
const signer = provider.getSigner(walletAddress);
let abi = [
" function Unite(string memory _spouse1, string memory _spouse2)"
];
const contractAddress = "0x2556Ff7f7F1c013bBB60bD120E1828032Cd84cc4"; //WeddingFactory Contract
const contract = new ethers.Contract(contractAddress, abi, signer);
console.log("sending the contract");
tx = await contract.Unite(spouse1,spouse2);
console.log(tx);
console.log("finished");
} else {
alert("Please enter 2 names");
}
}

Try creating your abi method in web3client.app.
It has code generater for ether.js which yoy can use directly in your app.
If there is an actual issue in your contract it would fail there itself.

Related

Passing a Smart Contract Function Parameter Derived from a Struct to Its Interaction Script

I'm trying to interact with a contract I just deployed with a JavaScript file on HardHat. However, I'm getting an error; I know why it's being caused, but I don't know how to fix it. In my contract I have a struct defined as FlashParams, and I have a function that uses it as a parameter as seen here (it's only part of my code since I know the rest of it isn't causing any issues):
//fee1 is the fee of the pool from the initial borrow
//fee2 is the fee of the first pool to arb from
//fee3 is the fee of the second pool to arb from
struct FlashParams {
address token0;
address token1;
uint24 fee1;
uint256 amount0;
uint256 amount1;
uint24 fee2;
uint24 fee3;
}
// fee2 and fee3 are the two other fees associated with the two other pools of token0 and token1
struct FlashCallbackData {
uint256 amount0;
uint256 amount1;
address payer;
PoolAddress.PoolKey poolKey;
uint24 poolFee2;
uint24 poolFee3;
}
/// #param params The parameters necessary for flash and the callback, passed in as FlashParams
/// #notice Calls the pools flash function with data needed in `uniswapV3FlashCallback`
function initFlash(FlashParams memory params) external onlyOwner {
PoolAddress.PoolKey memory poolKey =
PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee1});
IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
In my JS file I'm trying to pass the parameter from the contract as an argument in .initFlash(), but I don't know how to. Here's my JS file:
async function main() {
const address = '0x90A39BaC0D3A796b52f0394b9A97897d8F26eB1c8';
const PairFlash = await ethers.getContractFactory('PairFlash');
const pairflash = await PairFlash.attach(address);
const value = await pairflash.initFlash();
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
And here is the error:
Error: missing argument: passed to contract (count=0, expectedCount=1, code=MISSING_ARGUMENT, version=contracts/5.5.0)
reason: 'missing argument: passed to contract',
code: 'MISSING_ARGUMENT',
count: 0,
expectedCount: 1
Does anybody know how to fix this? Thank you!
I figured it out. The inputs for the JS file should be an a array format because this is the equivalent of a struct in solidity.

How to Approve spend of one Token with Web3.js?

I seek via my web page of test to create a button which allows to authorize the expenditure of a Contract (Token)..
If I go to the page and I click I would like the script to load web3 (it loads well) then if I press the button that Metamask authorizes the spending of the contract.
Metamask opens fine and does request the connection for my test site on the BSC in Web3js. However I can't find the exact code for the approve function.
Here is the code:
<head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/web3/1.7.0/web3.min.js'></script>
</head>
<button onclick="approvebutton();">Approve button to authorize tokens to be spent</button>
<script type="text/javascript">
if (typeof window.ethereum !== 'undefined') {
ethereum.request({ method: 'eth_requestAccounts' });
} else {
alert('Please install metamask')
}
var Web3 = require('web3');
const web3 = new Web3('https://bsc-dataseed1.binance.org:443');
async function approvebutton() {
/// APPROVE FUNCTION WITH THE CONTRACT
}
</script>
I tried this approach but it doesn't work (metamask confirmation won't show up):
if (typeof window.ethereum !== 'undefined') {
ethereum.request({ method: 'eth_requestAccounts' });
} else {
alert('Please install metamask')
}
var Web3 = require('web3');
const web3 = new Web3('https://bsc-dataseed1.binance.org:443');
const Contract = ('0xContractAddress');
const spenderAdr = ('0xSpenderAddress');
const amount = ('AmountTokensNumber')
async function approvebutton(Contract,spenderAdr){
Contract.methods.approve(spenderAddr, amount).send({
from: ownerAddr
})
}
Metamask won't show up to confirm the TX.
First of all, the approve method takes 2 parameters, the spender and the amount so it will be something like this:
Contract.methods.approve(spenderAddr, amount).send({
from: ownerAddr
})
The gas parameter is optional.
From the example code, I think you're missing the ABI (or Json Interface) for the contract and the instantiation of the contract via web3.eth.Contract() with the ABI and the contract address.
var Contract = new web3.eth.Contract(jsonInterface[, address][, options])
From that Contract instance you can then call the methods in the ABI, and that should trigger the Metamask modal.
From the docs:
Contract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'})
.then(function(receipt){
// receipt can also be a new contract instance, when coming from a "contract.deploy({...}).send()"
});
Or in your case, something along the lines of:
await Contract.methods.approve(spenderAddr, amount).send({ from: ownerAddr })
I also think that you're missing an await inside the approvebutton() function, to await the "promisevent" that the method call on the contract returns.
(Source https://web3js.readthedocs.io/en/v1.2.11/web3-eth-contract.html#, https://web3js.readthedocs.io/en/v1.2.11/web3-eth-contract.html#id26)

How to send solana via my app in vanilla js?

Trying to do a simple send and receive function in Solana with vanilla JS.
Below is my send function that works fine, and now I want a receive function.
Where the provider would get Solana transferred from my treasury wallet.
I'm not sure what approach I should have, just starting out. Is there a way to just move things around in this function? Or do I have to have a totally different approach?
Thanks!
async function transferSOL() {
// Detecing and storing the phantom wallet of the user (creator in this case)
var provider = phantom;
// Establishing connection
var connection = new web3.Connection(
web3.clusterApiUrl('devnet'),
);
var transaction = new web3.Transaction().add(
web3.SystemProgram.transfer({
fromPubkey: provider.publicKey,
toPubkey: treasuryWallet.publicKey,
lamports: 0.1 * web3.LAMPORTS_PER_SOL - 100
}),
);
// Setting the variables for the transaction
transaction.feePayer = await provider.publicKey;
let blockhashObj = await connection.getRecentBlockhash();
transaction.recentBlockhash = await blockhashObj.blockhash;
// Request creator to sign the transaction (allow the transaction)
let signed = await provider.signTransaction(transaction);
let signature = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(signature);
}
If you want to transfer from your treasury to the user, then you must sign for treasuryWallet somehow. In your case, it looks like you already have the keypair available to you in your app, so you can simply do:
var transaction = new web3.Transaction().add(
web3.SystemProgram.transfer({
toPubkey: provider.publicKey,
fromPubkey: treasuryWallet.publicKey,
lamports: 0.1 * web3.LAMPORTS_PER_SOL - 100
}),
);
// Setting the variables for the transaction
transaction.feePayer = treasuryWallet.publicKey;
let blockhashObj = await connection.getRecentBlockhash();
transaction.recentBlockhash = await blockhashObj.blockhash;
// Request creator to sign the transaction (allow the transaction)
transaction.sign(treasuryWallet);
let signature = await connection.sendRawTransaction(transaction.serialize());
await connection.confirmTransaction(signature);
Note that this is very unsafe! Everyone who uses your web app has full access to the treasury funds if the keypair is exposed, since they can just sign all the transactions that they want with it.

Question about signature verification using Cloud KMS

I'm trying to verify a signature generated with Google's cloud KMS, but I keep getting invalid responses.
Here's how I'm testing it:
const versionName = client.cryptoKeyVersionPath(
projectId,
locationId,
keyRingId,
keyId,
versionId
)
const [publicKey] = await client.getPublicKey({
name: versionName,
})
const valueToSign = 'hola, que tal'
const digest = crypto.createHash('sha256').update(valueToSign).digest()
const [signResponse] = await client.asymmetricSign({
name: versionName,
digest: {
sha256: digest,
},
})
const valid = crypto.createVerify('sha256').update(digest).verify(publicKey.pem, signResponse.signature)
if (!valid) return console.log('INVALID SIGNATURE')
console.log('SIGNATURE IS VALID!')
// output: INVALID SIGNATURE
This code will always log 'INVALID SIGNATURE' unless I use the original message instead of its hash:
const valid = crypto.createVerify('sha256').update(valueToSign).verify(publicKey.pem, signResponse.signature) // true
But using a local private key, I'm able to sign messages and verify them using their hashes:
const valueToSign = 'hola, the tal'
const msgHash = crypto.createHash("sha256").update(valueToSign).digest('base64');
const signer = crypto.createSign('sha256');
signer.update(msgHash);
const signature = signer.sign(pk, 'base64');
const verifier = crypto.createVerify('sha256');
verifier.update(msgHash);
const valid = verifier.verify(pubKey, signature, 'base64');
console.log(valid) // true
Why is it? Is there something different about kms signatures?
Based on this example from the crypto module documentation and your observations, I'd say that you might've misunderstood how client.asymmetricSign works. Let's analyze what happens:
Your local private key code:
const valueToSign = 'hola, the tal'
// Create sha256 hash
const msgHash = crypto.createHash("sha256").update(valueToSign).digest('base64');
// Let signer sign sha256(hash)
const signer = crypto.createSign('sha256');
signer.update(msgHash);
const signature = signer.sign(pk, 'base64');
// We now got sign(sha256(hash))
// Let verifier verify sha256(hash)
const verifier = crypto.createVerify('sha256');
verifier.update(msgHash);
const valid = verifier.verify(pubKey, signature, 'base64');
console.log(valid) // true
We are verifying sign(sha256(hash)) using verify(sha256(hash)).
Your KMS code:
const valueToSign = 'hola, que tal'
// Create sha256 hash
const digest = crypto.createHash('sha256').update(valueToSign).digest()
// Let KMS sign the hash
const [signResponse] = await client.asymmetricSign({
name: versionName,
digest: {
sha256: digest, // we already say "we hashed our data using sha256"
},
});
// We now got `sign(hash)`, NOT `sign(sha256(hash))` (where hash == digest)
// Let verifier verify sha256(hash)
const valid = crypto.createVerify('sha256').update(digest).verify(publicKey.pem, signResponse.signature)
We are verifying sign(hash) using verify(sha256(hash)).
Basically, locally you are signing your hash and verifying the signed hash. With KMS you are signing your data and verifying the signed hash, which is actually your signed data, hence your 2nd attempt with .update(valueToSign) works.
Solution? Hash your sha256 hash again before letting KMS sign it, since KMS expects the sha256 hash of the to-be-signed data, while the crypto expects the to-be-signed data (which it'll hash itself given the algorithm you passed to createSign).
The answer is very similar to the one from Kevin but from a different point of view, in other words.
When you use crypto.createSign(<algorithm>) and crypto.createVerify(<algorithm>) you are indicating the digest algorithm that will be used for signature creation and verification, respectively.
When you call update on the returned Sign and Verify objects you need to provide your data as is, crypto will take care of digesting that information as appropriate when you sign or verify it later.
In contrast, the GCP KMS asymmetricSign operation requires a message digest produced with the designated algorithm over your original data as argument. This is why you need to calculate the message digest with crypto.createHash first.
But please, as indicated, be aware that this fact doesn't change the behavior of the crypto verification process, it always requires the original data as input, this is why your code works when you pass your original data without hashing.
Although you provided a working example in your question for reference the GCP documentation provides additional ones.

Variables don't read properly from JS test scripts to Solidity Contract

So I don't know if I phrased the title properly, but I'm having problems with variables in my Javascript Test scripts. When i try to send a variable in my test script to the solidity contract it doesn't receive the same variable in the events emitted.
Here's my JS Test Script
const NFCertificate = artifacts.require("../contracts/NFCertificate.sol");
contract("NFCertificate", accounts => {
let account1 = 0x49f0C9b4Ab0b0DedDABf8b62e6089C9b20f936fa;
let certNum = 23477264530040661;
it("It should make first account an owner", async () => {
let instance = await NFCertificate.deployed();
let owner = await instance.owner();
assert.equal(owner, account1);
});
it("It should send a token and save it", async () => {
let instance = await NFCertificate.deployed();
await instance.createCertificate("hello", "world", "www", account1, {from: "0x49f0C9b4Ab0b0DedDABf8b62e6089C9b20f936fa"});
await instance.checkTokenStatus(certNum);
//await instance.checkOwnerStatus(account1, certNum);
//await instance.destroyCertificate(certNum, {from: "0x49f0C9b4Ab0b0DedDABf8b62e6089C9b20f936fa"});
assert.deepEqual(0, 1, "...");
});
});
Note the values given in the "account1" and "certNum" variables. Below is the event from the contract when I run the test script.
returnID(ID: 16514060434978983)
returnID(ID: 23477264530040661)
returnID(ID: 123477264530040661)
Transfer(from: <indexed>, to: <indexed>, tokenId: <indexed>)
ReturnNewCertificate(index: 0, CID: 23477264530040661, msgsender: 0x49f0c9b4ab0b0deddabf8b62e6089c9b20f936fa, onwershipHistory: 0x49f0c9b4ab0b0b7d8d98c5f33841ce0d00000000)
ReturnStatus(status: false, CID: 23477264530040660)
Notice how the last two events have some discrepancies in the variables.
First, the CID number in the event "ReturnNewCertificate" is generated within the contract based on sha256 algorithm. I hardcoded this same number as the value for the "certNum" variable in the JS script and sent it through the "checkTokenStatus" function that checks to see if this ID exists. However from the "ReturnStatus" event we can see the ID that was sent from the test script has the last digit wrong, from "1" to a "0" (compare CID's in both events).
This same problem can be seen when sending the "account1" variable through the "createCertificate" function, check the "ownershipHistory" variable within the "ReturnNewCertificate" event and compare that to the account1 variable in the test script.
Below are the solidity functions I am running within the test script.
function createCertificate(string title, string data, string url, address owner) public returns (uint certificateID, uint licenseID, uint assetID) {
assetID = generateAssetID(data);
AssetIDS.push(assetID);
certificateID = generateCertificateID(assetID, owner);
Certificate memory certificate = Certificate(Certificates.length, certificateID, assetID, 0, url, title, owner, owner, new address[](0), true);
uint index = Certificates.push(certificate)-1;
CIDtoIndex[certificateID] = index;
Certificates[index].ownershipHistory.push(owner);
//License
licenseID = createLicense(certificate.index, certificateID, owner, owner);
//ERC721 Mint
NFCProtocols.mint(owner, certificateID);
emit ReturnNewCertificate(certificate.index, certificate.certificateID, msg.sender, Certificates[certificate.index].ownershipHistory);
}
function checkTokenStatus(uint certificateID) public {
bool status = NFCProtocols.exists(certificateID);
emit ReturnStatus(status, certificateID);
}
Finally I'm sorry If this is all a little confusing, I'm a little new to coding in general so my code might not be the prettiest. Also Ive been scratching my head trying to figure out the problem for a few hours now, so any help would be appreciated.
Found the answer, when declaring variables in Javascript declare them like such:
let account1 = web3.eth.accounts[0];
let certNum = web3.toBigNumber('23282430602617218');

Categories

Resources