sendAndConfirmTransaction bug? - solana-web3js

In my application I use the signatures returned from web3.sendAndConfirmTransaction to do some offline/async reporting relating to the fees incurred by my transactions.
i.e. i use the signature to retrieve the transaction and then use the transaction?.meta?.fee field.
I have noticed though, when my transaction contains 2 instructions (in my example below) that the signature returned only contains the fee relating to 1 of the instructions. When I check the transaction history of my phantom wallet I can clearly see 2 separate fees - one for each instruction
async createTokenMetadataForToken(
business,
token_type
) {
const mint_authority = web3.Keypair.fromSeed(
derivePath(
`m/44'/501'/0'/0'`,
Bip39.mnemonicToSeedSync(
JSON.parse(business.keys).MINTER_SEED
).toString("hex")
).key
);
const metadata = await Metadata.getPDA(token_type.token_address);
const host = (config.nodeEnv == 'prod') ? 'https://<url>' : 'https://<url>'
const createMetadataTx = new CreateMetadataV2(
{ feePayer: mint_authority.publicKey },
{
metadata,
metadataData: new DataV2({
uri: `${host}/token_type/${token_type.token_type_id}/metadata`,
name: token_type.name,
symbol: token_type.symbol,
sellerFeeBasisPoints: 100,
creators: null,
collection: null,
uses: null,
tokenStandard: TokenStandard.FungibleAsset,
}),
updateAuthority: mint_authority.publicKey,
mint: new web3.PublicKey(token_type.token_address),
mintAuthority: mint_authority.publicKey,
}
);
const connection = getConnection(token_type.cluster);
const transaction = new web3.Transaction();
console.log("creating metadata")
transaction.add(createMetadataTx)
if(token_type?.equity_total_supply > 0){
console.log("also creating equity in same trx..")
//look up token
const token = new Token(
connection,
new web3.PublicKey(token_type.token_address),
TOKEN_PROGRAM_ID,
mint_authority
);
const recipientTokenAddress = await token.getOrCreateAssociatedAccountInfo(
new web3.PublicKey(mint_authority.publicKey)
);
transaction.add(
Token.createMintToInstruction(
TOKEN_PROGRAM_ID,
new web3.PublicKey(token_type.token_address),
recipientTokenAddress.address,
mint_authority.publicKey,
[mint_authority],
token_type?.equity_total_supply
)
)
}
const sig = await web3.sendAndConfirmTransaction(connection, transaction, [mint_authority], {
skipPreflight: false
})
return sig; //This signature only contains fee of one of the parts of the transaction
}

Related

Web3: My deployment gets stuck over Test Eth Network. [Error: Failed to check for transaction receipt: with contract.deploy()]

I trying to deploy my contract using node. I am following a youtube tutorial https://www.youtube.com/watch?v=3oaJynB0nKc&list=PLzb46hGUzitDd39YzB1YvZqeIXXtmBrHX&index=26.
I'm following everything according to this tutorial but every time I run "node deploy.js", I either get timeout error: (Note I am not using any truffle framework yet.)
process["on"]("unhandledRejection", function (reason) { throw reason; });
^
Error: Failed to check for transaction receipt:
{}
at Object._fireError (C:\Users\schit\Desktop\Solidity Dapp\inbox\node_modules\web3-utils\lib\index.js:49:17)
at C:\Users\schit\Desktop\Solidity Dapp\inbox\node_modules\web3-core-method\lib\index.js:246:23
My code is as follows:
const HDWalletProvider = require('#truffle/hdwallet-provider');
const Web3 = require('web3');
const interface = require('./compile.js');
let abi = interface.abi;
let bytecode = interface.bytecode;
var mnemonic = '12-word mnemonic';
var endpoint = 'https://ropsten.infura.io/v3/<key>';
const provider = new HDWalletProvider({
mnemonic: {
phrase: mnemonic
},
providerOrUrl: endpoint
});
console.log('********DEBUG*LOG**********2');
const options = {
transactionConfirmationBlocks: 1
};
const web3 = new Web3(provider, null, options);
console.log('********DEBUG*LOG**********3')
const deploy = async () => {
const accounts = await web3.eth.getAccounts();
var balance = await web3.eth.getBalance(accounts[0]);
console.log('Account balance: ', web3.utils.fromWei(balance, "ether"));
console.log('Attempting to deploy from account', accounts[0]);
var contract = new web3.eth.Contract(abi);
**const result = await contract
.deploy({data: '0x' + bytecode, arguments: ['Hi there!']})
.send({from: accounts[0], gas: '1000000'})
.on ('error', console.error)
.on ('transactionHash', console.log)
.on ('receipt', console.log);**
console.log('Contract deployed to', result.options.address);
};
deploy();

Possible to create token and metadata in one transaction?

Am I correct in saying that it's not possible to mint(create) an spl token and token metadata in the one transaction ?
Here is me cerating the mpl token metadata for a previously cerated spl token:
const createMetadataTx = new CreateMetadataV2(
{ feePayer: this.appTreasPair.publicKey },
{
metadata,
metadataData: new DataV2({
uri: `${host}/token_type/${token_type.token_type_id}/metadata`,
name: token_type.name,
symbol: token_type.symbol,
sellerFeeBasisPoints: 100,
creators: null,
collection: null,
uses: null,
tokenStandard: TokenStandard.FungibleAsset,
}),
updateAuthority: this.appTreasPair.publicKey,
mint: new web3.PublicKey(token_type.token_address),
mintAuthority: this.appTreasPair.publicKey,
}
);
const connection = solanaUtils.getConnection(token_type.cluster);
const transaction = new web3.Transaction();
console.log("creating metadata")
transaction.add(createMetadataTx)
const sig = await web3.sendAndConfirmTransaction(connection, transaction, [this.appTreasPair], {
skipPreflight: skipPreflight
})
the mint field needs the address of the spl token which I can only get if I do the spl token creation in an initial separate transaction
It is possible. E.g. candy machine is creating a SPL token and then assign metadata to it.
If you want a NFT: https://solanacookbook.com/references/nfts.html#mint-the-nft
If you rather want a standard SPL token the current method is create the SPL Token and then add it to the Solana Token list https://github.com/solana-labs/token-list (a better solution is WIP)

Cypress crashes when test that uses gmail-tester library finished it work

I'm was trying to use "gmail-tester" library to verify the account creation message.
https://www.npmjs.com/package/gmail-tester
It seems that I settled up everything as it was supposed to be done. When my test is finished I supposed to get an assertion in cypress such as this
Instead, cypress is awaiting for a message for 30seconds
, then browser crashes and I got this
Does anyone know what would cause the problem?
I have managed to complete all steps mentioned in this tutorial:
https://levz0r.medium.com/how-to-poll-a-gmail-inbox-in-cypress-io-a4286cfdb888
../cypress/plugins.index.js
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* #type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
const path = require("path");
const gmail = require("gmail-tester");
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// ...
on("task", {
"gmail:check": async args => {
const { from, to, subject } = args;
const email = await gmail.check_inbox(
path.resolve(__dirname, "credentials.json"), // credentials.json is inside plugins/ directory.
path.resolve(__dirname, "gmail_token.json"), // gmail_token.json is inside plugins/ directory.
subject,
from,
to,
10, // Poll interval (in seconds)
12 // Maximum poll interval (in seconds). If reached, return null, indicating the completion of the task().
);
return email;
}
});
};
testCase.spec.js
import Navigation from '../../../utils/navigation.spec'
import LoginPage from '../../../pageobject/login/login-page'
describe("New user registration", async function() {
beforeEach(() => {
cy.visit(Navigation.Login)
})
it.only("Reset Form: Email is delievered", function() {
const test_id = new Date().getTime();
const incoming_mailbox = `userautomatedtest+${test_id}#gmail.com`;
// const password = uuidv1().split("-")[0];
const login = new LoginPage();
const username = "Cypress" + test_id;
const password = "111#wZOO";
login.registerButton()
.usernameInput(username)
.emailInput(incoming_mailbox)
.firstNameInput("Name")
.lastNameInput("Surname")
.passwordInput(password)
.repeatPasswordInput(password)
.registerButton()
//assert
cy.contains('Registration succeeded').should('be.visible')
cy.task("gmail:check", {
from: "dev.mailer.no.reply#gmail.com",
to: incoming_mailbox,
subject: "Registration confirmation"
})
.then(email => {
assert.isNotNull(email, `Email was not found`);
});
});
});
btw: in documentation is mentioned that by changing this number we can manipulate awaiting time for checking email. In my case, I'm changing this value and nothing is happening.
This is some problem with the OAuth consent screen, probably access given is not correct, or the GMail API isn't enabled.
Using the most recent version of this package, I had the same issue with the plugins/index.js crashing.
I solved this by adjusting the options-parameter to match the gmail task package function check_inbox.
module.exports = (on, config) => {
on("task", {
"gmail:check": async (args) => {
const { from, to, subject } = args;
const email = await gmail.check_inbox(
path.resolve(__dirname, "credentials.json"),
path.resolve(__dirname, "gmail_token.json"),
{
subject: subject,
from: from,
to: to,
wait_time_sec: 10,
max_wait_time_sec: 30,
}
);
return email;
},
});
};

How can I register a Client through an express REST API in Hyperledger fabric

I want to register a user of the application through a REST API. I have already enrolled the admin and a user through the enrollAdmin.js and registerUser.js function but I want to call these functions through the node SDK and register users dynamically with there username (UUID) so that it's completly anonymous.
As the username I want to create a unique UUID and save that in the world state but also save that UUID on an off-chain database together with the personal information like password and name so that I can associate the personal information with the UUID.
Right know I'm confused by all the different steps I have to do to register a new user:
In what order do I have to enroll and register the user and should they all be defined in the express API or in chaincode?
This is my first approach of creating the REST Api and till now I have only defined the layout, the connection profile and wallet.
I would appreciate if somebody could help me implement the registration process in the express REST API so that an Identity for the UUID gets saved in the world state.
Thanks in advance.
server.js
'use strict';
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
// Setting for Hyperledger Fabric
const { Wallets, FileSystemWallet, Gateway } = require('fabric-network');
const path = require('path');
const fs = require('fs');
const channelName = 'mychannel';
const mspOrg1 = 'Org1MSP';
const walletPath = path.join(__dirname, '..', 'wallet');
const ccpPath = path.resolve(__dirname, '..', 'connection-org1.json');
//register
app.post('/api/register', async function (req, res) {
try{
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
} catch (error) {
}
});
//login
app.post('/api/login', async function (req, res) {
try{
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
} catch (error) {
}
});
app.listen(3000, ()=>{
console.log("***********************************");
console.log("API server listening at localhost:3000");
console.log("***********************************");
});
The process of how you want it to be is simple. In the middle, the off-chain database is used as a mapping table. I wrote only the core process logic.
/api/v1/register
validation check
Validate that the user's id is unique, that the required information value is missing, that the regular expression is correct, and that there is no wrong information.
generate random UUID
Create a random, unique uuid first. npm/uuid
const UUID = uuid.v4();
register/enroll user to fabric-ca
Perform the registration process as a user of the fabric. The information that goes into this process is UUID, and the user's information will not be stored in the blockchain.
fabricUser is a newly created class, and returns the result after fabric user registration and enroll process are performed by the Enroll method.
enrollment = await fabricUser.Enroll(UUID);
await wallet.put(enrollment);
insert to database
While saving the user information in the database, map it by storing the UUID created above.
The database was created as an example, assuming mongodb.
db.collection('User').insertOne({
'uuid': UUID,
'user_id': <input_user_id>,
...
});
/api/v1/login
The login process is as follows.
I don't know what authentication/authentication method you want to use, so I'll assume a token authentication method based on auth 2.0.
Verify the validity of the necessary information required for login and whether there is any incorrect information.
get UUID
generateAuthToken is a new function that generates JWT.
let res = await db.collection("User").findOne({'user_id': `<input_user_id>` });
return generateAuthToken(res.uuid);
/api/v1/invoke
Fabric resource request process is as follows.
Token validation and resource authorization check
get userName from token
getPayload is a function that gets the payload value located at the 1st index from the token.
const rawPayload = getPayload(token);
const jsonPayload = JSON.parse(rawPayload);
return jsonPayload
get wallet & invoke chaincode
The fabricChaincode is a function that wraps the invoke process of fabric-sdk. It is a function that executes invoke by inputting identity, chaincode information, and parameters, and returns a result.
const user = await db.collection("User").findOne({'user_id': jsonPayload.user_id });
const fabricIdentity = await wallet.get(user.uuid);
const res = fabricChaincode.invoke(fabricIdentity, `<your_chaincode_info>`, `<input_chaincode_params>`)
return res;
[EDIT]
Add it for your understanding.
fabricUser.js
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const fs = require('fs');
const path = require('path');
async function Enroll(user_id) {
try {
// load the network configuration
const ccpPath = path.resolve(__dirname, '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
// Create a new CA client for interacting with the CA.
const caURL = ccp.certificateAuthorities['ca.org1.example.com'].url;
const ca = new FabricCAServices(caURL);
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = await Wallets.newFileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userIdentity = await wallet.get(user_id);
if (userIdentity) {
console.log(`An identity for the user ${user_id} already exists in the wallet`);
return;
}
// Check to see if we've already enrolled the admin user.
const adminIdentity = await wallet.get('admin');
if (!adminIdentity) {
console.log('An identity for the admin user "admin" does not exist in the wallet');
console.log('Run the enrollAdmin.js application before retrying');
return;
}
// build a user object for authenticating with the CA
const provider = wallet.getProviderRegistry().getProvider(adminIdentity.type);
const adminUser = await provider.getUserContext(adminIdentity, 'admin');
// Register the user, enroll the user, and import the new identity into the wallet.
const secret = await ca.register({
affiliation: 'org1.department1',
enrollmentID: user_id,
role: 'client'
}, adminUser);
const enrollment = await ca.enroll({
enrollmentID: user_id,
enrollmentSecret: secret
});
const x509Identity = {
credentials: {
certificate: enrollment.certificate,
privateKey: enrollment.key.toBytes(),
},
mspId: 'Org1MSP',
type: 'X.509',
};
await wallet.put(user_id, x509Identity);
console.log(`Successfully registered and enrolled admin user ${user_id} and imported it into the wallet`);
} catch (error) {
console.error(`Failed to register user ${user_id}: ${error}`);
process.exit(1);
}
}
module.exports = {
Enroll
}
api.js
const uuid = require('uuid');
const fabricUser = require('./fabricUser);
const UUID = uuid.v4();
let res = await fabricUser.Enroll(UUID);
console.log(res);

how to upload images as a signed request with Cloudinary/Angular(5) and Ionic(3)?

Cloudinary have a basic.js example which I'm trying to implement in my Ionic/Angular project.
Problem is, for some reason the Ionic version of "#cloudinary/angular-5.x" always uses the unsigned_upload feature, and I want to be able to transform it before I upload, same as the Cloudinary example.
Transformation requires signed upload not unsigned upload.
Since there are many versions out-there, and most of the examples don't work, mine is:
Ionic: 3
Angular: 5.2.11
Cloudinary:
"cloudinary": "^1.11.0",
"cloudinary-core": "^2.5.0",
"#cloudinary/angular-5.x": "^1.0.2"
basic.js
My configuration is inside the .env variable with the structure mentioned in cloudinary.config
var dotenv = require('dotenv');
dotenv.load();
var fs = require('fs');
var cloudinary = require('cloudinary').v2;
// set your env variable CLOUDINARY_URL or set the following configuration
/*cloudinary.config({
cloud_name: '',
api_key: '',
api_secret: ''
});*/
var url = "http://res.cloudinary.com/demo/image/upload/couple.jpg"
cloudinary.uploader.upload(url,{"tags":"basic_sample","width":500,"height":500,"crop":"fit","effect":"saturation:-70"} ,
function(err,image){
if (err){
console.warn(err);
return;
}
console.log("* "+image.public_id);
console.log("* "+image.url);
// Transform image
cloudinary.url(image.public_id,
{
width: 200,
height: 150,
crop: "fill",
gravity: "face",
radius: 10,
effect:"sepia",
format: "jpg"
}
));
});
I'm able with the following code to upload it unsigned
Ionic unsigned request
ngOnInit(): void {
const uploaderOptions: FileUploaderOptions = {
url: 'https://api.cloudinary.com/v1_1/' + this.cloudinary.config().cloud_name + '/upload',
autoUpload: false,
isHTML5: true,
removeAfterUpload: true,
headers: [{
name: 'X-Requested-With',
value: 'XMLHttpRequest'
}]
};
this.uploader = new FileUploader(uploaderOptions);
// Add custom tag for displaying the uploaded photo in the list
this.uploader.onBuildItemForm = (fileItem: any, form: FormData): any => {
form.append('upload_preset', this.cloudinary.config().upload_preset);
form.append('public_id', 'subfolder/' + this.UUID);
form.append('file', fileItem);
fileItem.withCredentials = false;
return { fileItem, form };
};
}
Ionic signed request
So in order to transform my images, I need to use parameter called eager
form.append('eager', 'c_crop,w_191,h_145,g_face,z_0.7');
But then I get the below error
Upload completed with status code 400
{
"message": "Eager parameter is not allowed when using unsigned upload.
Only upload_preset,callback,public_id,folder,tags,context,face_coordinates,custom_coordinates,source upload parameters are allowed.
}
When I remove the preset to "tell" it that maybe this is a signed request, I get the above error + Upload preset must be specified when using unsigned upload
So I'm not sure how I'm suppose to "tell" it - use signed request, and take my configuration from .env or CloudinaryModule.forRoot({Cloudinary}, cloudinaryConfiguration as CloudinaryConfiguration), etc ...
For signed upload, you need to create a signature. During post request, you have to attach it with form.
Signature is SHA1 hexadecimal string which is consists of timestamp(unixtime), public_id (any text) and your cloudinary API_SECRET.
Here is my workable sample
private generateSignature() {
this.public_id = `image_${Date.now()}`; // I like to make it unique.
this.unixtime = Date.now() / 1000 | 0;
return CryptoJS.SHA1(`public_id=${this.public_id}&timestamp=${this.unixtime}${this.API_SECRET}`).toString()
}
here I use CryptoJS for encription.
Append this signature with form body before send API request.
for example
initFileUploader(): void {
const self = this;
self.uploader = new FileUploader({
url: 'https://api.cloudinary.com/v1_1/your_cloud_name/upload',
allowedMimeType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'],
maxFileSize: 524288,//512 KB
autoUpload: true,
removeAfterUpload: true,
isHTML5: true,
headers: [
{
name: 'X-Requested-With',
value: 'XMLHttpRequest'
}
]
});
self.uploader.onAfterAddingFile = (file) => {
file.withCredentials = false;
};
self.uploader.onSuccessItem = (item, response, status) => {
const resp = <any>JSON.parse(response);
if (resp) {
this.onSuccess.emit(resp);
} else {
this.onError.emit('An error occured during upload. Please retry');
}
};
self.uploader.setOptions(self._uploaderOptions);
self.uploader.onBuildItemForm = (fileItem: any, form: FormData): any => {
let signature = this.generateSignature();
form.append('timestamp', this.unixtime.toString());
form.append('public_id', this.public_id);
form.append('api_key', this.API_KEY); //your cloudinary API_KEY
form.append('signature', signature);
return { fileItem, form };
};
}
I use ng2-file-upload for uploading...
uploading images via signed method
Signed uploads require an authentication signature to be generated on your server using a function method or string method, and as such, signed upload
The current Angular SDK is outdated so we follow these steps to implement our signed upload.
Manually generate Signature via string method in Angular
To manually generate your own POST request, you need to authenticate the request with a signature based on the parameters you use in the request. The signature is a hexadecimal message digest (hash value) created with the SHA-1 or SHA-256 (Secure Hash Algorithm) cryptographic function.
You can manually generate the comparison signature instead of using the Cloudinary SDK’s api_sign_request method.
For example, if your API secret is abcd, your API key is 1234, the Unix time now is 1315060510 and you are posting a request to upload a file from ‘https://www.example.com/sample.jpg', set its Public ID as sample_image, and eagerly generate 2 images:
Parameters to sign:
timestamp: 1315060510
public_id: sample_image
eager: w_400,h_300,c_pad|w_260,h_200,c_crop
Serialized sorted parameters in a single string:
eager=w_400,h_300,c_pad|w_260,h_200,c_crop&public_id=sample_image&timestamp=1315060510
String including the API secret that is used to create the SHA-1 signature:
eager=w_400,h_300,c_pad|w_260,h_200,c_crop&public_id=sample_image&timestamp=1315060510abcd
Generate Signature in Angular
Using a native js function for hashing messages with the SHA-1 algorithm
First Install sha1
npm install sha1
Then import the package into the app
import sha1 from ‘sha1’;
Generate UUID for Public ID
Another thing we did so each upload has a unique ID was to using UUID package to generate a unique Public ID for each upload
npm install uuid
import * as uuid from ‘uuid’;
on NgInit we generate the UUID using
this.uuidValue = `${uuid.v4().toLowerCase()}`;
we the use method sha1(string) Returns the SHA-1 hash of the given message.
The result is a SHA-1 hexadecimal result:
b4ad47fb4e25c7bf5f92a20089f9db59bc302313
signuploadform() {
const timestamp = Math.round(new Date().getTime() / 1000);
const apiSecret = this.environmentService.getValue('CLOUDINARY_API_SECRET');
const api_key = this.environmentService.getValue('CLOUDINARY_API_KEY');
const signature = sha1(
'eager=c_pad,h_300,w_400|c_crop,h_200,w_260&folder=identification/NGA&public_id=' +
this.uuidValue +
'&timestamp=' +
timestamp +
apiSecret
);
return {timestamp, signature, api_key};
}
Post the Upload
Now that the signature has been generated we then post using the parameter as shown in the code below
folder
public_id
file
api_key
timestamp
signature
HTML
<input hidden (change)=”onFileChange($event)” #fileInput accept=”image/*” type=”file” id=”file”>
TS
onFileChange(event: any) {
this.uploadFile(event.target.files[0]);
}
uploadFile(file: File) {
const signData = this.signuploadform();
const formData = new FormData();
formData.append('eager', 'c_pad,h_300,w_400|c_crop,h_200,w_260');
formData.append('folder', 'identification/NGA');
formData.append('public_id', this.uuidValue);
formData.append('file', file);
formData.append('api_key', signData.api_key);
formData.append('timestamp', signData.timestamp.toString());
formData.append('signature', signData.signature);
const url =
'https://api.cloudinary.com/v1_1/' +
this.environmentService.getValue('CLOUDINARY_CLOUD_NAME') +
'/auto/upload';
this.isLoading = true;
this.http
.post(url, formData)
.pipe(map((x: any) => x.secure_url as string))
.subscribe({
next: res => {
this.identification = res;
this.uploadTitle = 'ID Uploaded';
this.uploadStatus = true;
from(
Swal.fire({
icon: 'success',
title: 'Successfully uploaded',
showConfirmButton: true,
})
);
},
error: error => {
this.isLoading = false;
from(
Swal.fire({
icon: 'error',
title: 'Please check your image again',
showConfirmButton: true,
})
);
},
complete: () => {
this.isLoading = false;
},
});
}