I have a problem when trying to add a new asset to an array of assets which are part of the participant as a reference.
Here I have SharedAccount participant controlled by its members who are connected via their share in the account.
I want to write a transaction for creating a new SharedAccount by one person. When a person submits a transaction, it should create a share asset if that person and add it to SharedAccount's shares array.
Here's how my code looks like
.cto:
...
participant SharedAccount identified by sharedAccountId {
o String sharedAccountId
--> Share[] shares
o Double balance
o Double originalBalance
}
asset Share identified by shareId {
o String shareId
--> Person shareHolder
o Double amount
}
transaction CreateSharedAccount {
--> Person creator
o String accountName
o Integer amount
}
...
.js:
...
/**
* #param {org.mistral.bulliongrower.CreateSharedAccount} createSharedAccount
* #transaction
*/
async function CreateSharedAccount(createSharedAccount) {
const factory = getFactory();
const NS = 'org.mistral.bulliongrower';
// create share
const share = factory.newResource(NS, 'Share', createSharedAccount.creator.personId + 'SHARE');
share.amount = createSharedAccount.amount;
share.shareHolder = createSharedAccount.creator;
share.shareHolder.balance -= createSharedAccount.amount;
const sharesRegistry = await getAssetRegistry(NS + '.Share');
await sharesRegistry.add(share);
const personRegistry = await getParticipantRegistry(NS + '.Person');
await personRegistry.update(share.shareHolder);
// create sharedAccount
const sharedAcc = factory.newResource(NS, 'SharedAccount', createSharedAccount.accountName);
sharedAcc.shares.push(share);
sharedAcc.balance = createSharedAccount.amount;
sharedAcc.originalBalance = createSharedAccount.amount;
const sharedAccRegistry = await getAssetRegistry(NS + '.SharedAccount');
await sharedAccRegistry.add(sharedAcc);
}
...
I'm not sure if I should use factory.newRelationship and how, when adding a share Asset to SharedAccount.
The error I get in the playground when trying to execute the transaction is
TypeError: Cannot read property 'push' of undefined
try to do this:
/**
* #param {org.mistral.bulliongrower.CreateSharedAccount} createSharedAccount
* #transaction
*/
async function CreateSharedAccount(createSharedAccount) {
const factory = getFactory();
const NS = 'org.mistral.bulliongrower';
// create share
const share = factory.newResource(NS, 'Share', createSharedAccount.creator.personId + 'SHARE');
//const share = factory.newRelationship(NS, 'Share', createSharedAccount.creator.personId + 'SHARE');
share.amount = createSharedAccount.amount;
//share.shareHolder = factory.newRelationship(NS, 'Share', createSharedAccount.creator.personId);
share.shareHolder = createSharedAccount.creator;
share.shareHolder.balance -= createSharedAccount.amount;
const sharesRegistry = await getAssetRegistry(NS + '.Share');
await sharesRegistry.add(share);
const personRegistry = await getParticipantRegistry(NS + '.Person');
await personRegistry.update(share.shareHolder);
// create sharedacc
const sharedAcc = factory.newResource(NS, 'SharedAccount', createSharedAccount.accountName);
//sharedAcc.shares = factory.newRelationship(NS, 'Share', createSharedAccount.creator.personId);
//sharedAcc.shares[0] = factory.newRelationship(NS, 'Share', share.shareId);
// define an array
let sharesArray = [];
sharesArray.push(share);
sharedAcc.shares = sharesArray;
sharedAcc.balance = createSharedAccount.amount;
sharedAcc.originalBalance = createSharedAccount.amount;
// use getParticipantRegistry not getAssetRegistry
const sharedAccRegistry = await getParticipantRegistry(NS + '.SharedAccount');
await sharedAccRegistry.add(sharedAcc);
}
your transaction code should be something like below - some of your references weren't right (take too long to point out all the changes, so you can refer below).
I added a test string (for 'Person') just to show what you would do (to have a reason to update that particular participant registry).
Seems to me that SharedAccount is an asset not a participant. And you would use the appropriate JS API to update that type of registry.
balance is not a field on Person (it is on SharedAccount), but your code was trying to refer to it.
I've left comments for 'alternative ways' for declarations and such like - just by way of info.
/**
* #param {org.mistral.bulliongrower.CreateSharedAccount} createSharedAccount
* #transaction
*/
async function CreateSharedAccount(createSharedAccount) {
const factory = getFactory();
const NS = 'org.example.trading';
// create share
const share = factory.newResource(NS, 'Share', createSharedAccount.creator.personId + 'SHARE');
share.amount = createSharedAccount.amount;
console.log("amount is " + share.amount);
share.shareHolder = createSharedAccount.creator;
// share.shareHolder.balance -= createSharedAccount.amount; // won't work - balance is a field on SharedAccount not Person - moved it below
const sharesRegistry = await getAssetRegistry(NS + '.Share');
await sharesRegistry.add(share);
share.shareHolder.newstr = "123"; // setting 'SOME' field (I added 'newstr' in my model, see below - so as to have a reason to update / give an example
const personRegistry = await getParticipantRegistry(NS + '.Person');
await personRegistry.update(share.shareHolder);
// create sharedAccount
const sharedAcc = factory.newResource(NS, 'SharedAccount', createSharedAccount.accountName);
//let idsArray = new Array(); // alternative, or use implicit below.
let idsArray = [] ;
let shareAssetRelationship = factory.newRelationship(NS, 'Share', share.getIdentifier());
idsArray.push(shareAssetRelationship); // only one element anyway
sharedAcc.shares = idsArray;
sharedAcc.balance = createSharedAccount.amount; // this is a new resource - so balance is eq to trxn amount ?
sharedAcc.originalBalance = createSharedAccount.amount; // original balance is nothing or 'balance' ?....anyway....
const sharedAccRegistry = await getAssetRegistry(NS + '.SharedAccount');
await sharedAccRegistry.add(sharedAcc);
}
The model I used is this:
participant Person identified by personId {
o String personId
o String newstr
}
asset SharedAccount identified by sharedAccountId {
o String sharedAccountId
--> Share[] shares
o Double balance
o Double originalBalance
}
asset Share identified by shareId {
o String shareId
--> Person shareHolder
o Double amount
}
transaction CreateSharedAccount {
--> Person creator
o String accountName
o Integer amount
}
Related
I'm trying to execute an Pancakeswap swapExactTokensForTokens using ethers.js but i just keep getting the error invalid response - sendTransaction. Unfortunatly the error doesnt contain any more usefull information then that :(
My code:
const provider = new ethers.providers.WebSocketProvider(config.network);
const tradeWallet = ethers.Wallet.fromMnemonic(config.mnemonic);
const account = tradeWallet.connect(provider);
const router = new ethers.Contract(
'0x10ED43C718714eb63d5aA57B78B54704E256024E',
[
'function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts)',
'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)'
],
account
);
[snip]
var amountIn = ethers.utils.parseUnits('0.001', 'ether');
var tokenIn = '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c';
var tokenOut = '0xd2de3fd31b5c9e1557cf329032615a2870a29ccd';
var gasPrice = '5000000000';
var gasLimit = '231795'
var amounts = await router.getAmountsOut(amountIn, [tokenIn, tokenOut])
const amountOutMin = amounts[1].sub(amounts[1].div(10));
// values at the time where:
// tokenIn: 100000000000000 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c (WBNB)
// tokenOut: 1810636794711288351 0xd2de3fd31b5c9e1557cf329032615a2870a29ccd
var tx = router.swapExactTokensForTokens(
amountIn,
amountOutMin,
[tokenIn, tokenOut],
addresses.recipient,
Date.now() + 1000 * 60 * 3, //10 minutes
{ gasPrice: gasPrice,
gasLimit: gasLimit
}
);
const receipt = await tx.wait();
use 'swapExactTokensForETHSupportingFeeOnTransferTokens',Because your 'tokenOut' token has a tax function
I have a concern since as I am new to using EntityFramework Core, that if I add an object, that I still do not have the id generated by the database, sending the object to it in the transaction, I would add it automatically, this is my code ,
public async Task<ServiceResult<Common.Entities.Company>> SaveCompany(Domain.Models.Company companyModel, Domain.Models.Administrator administratoModel)
{
ServiceResult<Common.Entities.Company> serviceResult = new ServiceResult<Common.Entities.Company>();
try
{
if (user == null && companyExistsRnc == false)
{
Common.Entities.Company myCompany = new Common.Entities.Company
{
CompanyId = companyModel.CompanyId, // The id has not been generated yet,
CompanyName = companyModel.CompanyName,
Rnc = companyModel.Rnc,
CountryId = companyModel.Country.CountryId,
Telephone = companyModel.Telephone,
PersonContact = companyModel.PersonContact,
Address = companyModel.Address,
PhotoPath = companyModel.PhotoPath,
IsActive = false,
};
await _companyRepository.SaveCompany(myCompany); // this is the method that I add the company object to the database and do the savechanges
Common.Entities.User myUser = new Common.Entities.User
{
FirstName = administratoModel.FirstName,
SecondName = administratoModel.SecondName,
FirstLastName = administratoModel.FirstLastName,
SecondLastName = administratoModel.SecondLastName,
GenderId = administratoModel.Gender.GenderId,
PhoneNumber = administratoModel.Telephone,
Email = administratoModel.Email,
UserName = administratoModel.Email,
IsActive = administratoModel.IsActive,
UserTypeId = (short)Common.Core.UserType.Administrator,
Company = myCompany, // here I send the my company object for when I do the savechanges, I think it will add it to me
};
await _userHelper.AddUserAsync(myUser, administratoModel.Password);
await _userHelper.AddUserToRoleAsync(myUser, Common.Core.UserType.Administrator.ToString());
Common.Entities.Administrator myAdministrator = new Common.Entities.Administrator
{
AdministratorId = administratoModel.AdministratorId,
FirstName = administratoModel.FirstName,
SecondName = administratoModel.SecondName,
FirstLastName = administratoModel.FirstLastName,
SecondLastName = administratoModel.SecondLastName,
GenderId = administratoModel.Gender.GenderId,
Email = administratoModel.Email,
Telephone = administratoModel.Telephone,
IsActive = true,
PhotoPath = administratoModel.PhotoPath,
UserTypeId = (short)Common.Core.UserType.Administrator,
Company = myCompany, // company object without the id
User = myUser, // user object without the id
};
await _administratorRepository.SaveAdministrator(myAdministrator);
serviceResult.Data = myCompany;
serviceResult.Message = "Compañia agregada!";
}
}
I am new to using entity framework core, and if in case I am wrong in what I am doing please indicate in which part I am doing it wrong, to correct, I await your comments and would appreciate the help,
I am in the need of listing the users data belonging to a specific group within the organization. The documentation does not specify if this is possible. I was really hoping there could be some kind of query that would allow this. For example email in (1#domain.com,2#domain.com). However, I don't see that being possible. The only way I could think to accomplish this would be:
Get a list of all the members in the group (https://developers.google.com/admin-sdk/directory/reference/rest/v1/members/list)
Get each user data by email (https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/get)
The problem with the above approach is that if a group contains 50+ members, this means that I have to make all that amount of requests, which is counter productive. Imagine how long that would take.
Any ideas? Greatly appreciate it.
Unfortunately I don’t think you can skip this two step process, but you can speed it up using batch requests. This
allows you to request up to 1000 calls in a single request. The steps would be:
Make a batch request to get all the members of all the groups you want (using members.list).
Make a batch request to get all the user info that you need using their id (using user.get).
Notice that the data in the result won’t be sorted, but they will be tagged by Content-ID.
References
Sending Batch Requests (Directory API)
Method: members.list (Directory API)
Method: users.get (Directory API)
I thought about the batching request a couple of hours after I posted the question. The problem with Node JS is that it does not has built in support for batch requests, unlike the php client library for example; Therefore, I had to spent some time implementing support for it on my own since I was not able to find any example. I'll share the solution in case it helps someone else or for my future reference.
async function getGroupMembersData(){
const groupEmail = "group#domain.com"; //google group email
const groupMembers = await getGroupMembers(groupEmail).catch(error=>{
console.error(`Error querying group members: ${error.toString()}`);
});
if(!groupMembers){ return; }
const url = "https://www.googleapis.com/batch/admin/directory_v1";
const scopes = ["https://www.googleapis.com/auth/admin.directory.user.readonly"];
const requests = [];
for(let i=0; i<groupMembers.length; ++i){
const user = groupMembers[i];
const request = {
email: user,
endpoint: `GET directory_v1/admin/directory/v1/users/${user}?fields=*`
};
requests.push(request);
}
const batchRequestData = await batchProcess(url, scopes, requests).catch(error=>{
console.error(`Error processing batch request: ${error.toString()}`);
});
if(!batchRequestData){ return; }
const usersList = batchRequestData.map(i=>{
return i.responseBody;
});
console.log(usersList);
}
//get group members using group email address
async function getGroupMembers(groupKey){
const client = await getClient(scopes); //function to get an authorized client, you have to implement on your own
const service = google.admin({version: "directory_v1", auth: client});
const request = await service.members.list({
groupKey,
fields: "members(email)",
maxResults: 200
});
const members = !!request.data.members ? request.data.members.map(i=>i.email) : [];
return members;
}
//batch request processing in groups of 100
async function batchProcess(batchUrl, scopes, requests){
const client = await getClient(scopes); //function to get an authorized client, you have to implement on your own
let results = [];
const boundary = "foobar99998888"; //boundary line definition
let batchBody = ""; const nl = "\n";
const batchLimit = 100; //define batch limit (max supported = 100)
const totalRounds = Math.ceil(requests.length / batchLimit);
let batchRound = 1;
let batchItem = 0;
let roundLimit = batchLimit;
do{
roundLimit = roundLimit < requests.length ? roundLimit : requests.length;
//build the batch request body
for(batchItem; batchItem<roundLimit; batchItem++){
const requestData = requests[batchItem];
batchBody += `--${boundary}${nl}`;
batchBody += `Content-Type: application/http${nl}`;
batchBody += `Content-Id: <myapprequest-${requestData.email}>${nl}${nl}`;
batchBody += `${requestData.endpoint}${nl}`;
}
batchBody += `--${boundary}--`;
//send the batch request
const batchRequest = await client.request({
url: batchUrl,
method: "POST",
headers: {
"Content-Type": `multipart/mixed; boundary=${boundary}`
},
body: batchBody
}).catch(error=>{
console.log("Error processing batch request: " + error);
});
//parse the batch request response
if(!!batchRequest){
const batchResponseData = batchRequest.data;
const responseBoundary = batchRequest.headers["content-type"].split("; ")[1].replace("boundary=", "");
const httpResponses = batchResponseParser(batchResponseData, responseBoundary);
results.push(...httpResponses);
}
batchRound++;
roundLimit += batchLimit;
} while(batchRound <= totalRounds);
return results;
};
//batch response parser
function batchResponseParser(data, boundary){
const nl = "\r\n";
data = data.replace(`--${boundary}--`,"");
const responses = data.split(`--${boundary}`);
responses.shift();
const formattedResponses = responses.map(i=>{
const parts = i.split(`${nl}${nl}`);
const responseMetaParts = (parts[0].replace(nl, "")).split(nl);
let responseMeta = {};
responseMetaParts.forEach(part=>{
const objectParts = part.split(":");
responseMeta[objectParts[0].trim()] = objectParts[1].trim();
});
const responseHeadersParts = parts[1].split(nl);
let responseHeaders = {};
responseHeadersParts.forEach(part=>{
if(part.indexOf("HTTP/1.1") > -1){
responseHeaders.status = part;
} else {
const objectParts = part.split(":");
responseHeaders[objectParts[0].trim()] = objectParts[1].trim();
}
});
const reg = new RegExp(`${nl}`, "g");
const responseBody = JSON.parse(parts[2].replace(reg, ""));
const formatted = {
responseMeta: responseMeta,
responseHeaders: responseHeaders,
responseBody: responseBody
};
return formatted;
});
return formattedResponses;
}
namespace com.biz
participant User identified by name {
o String name
}
participant Bank identified by name {
o String name
o Integer points
--> Document document optional
}
asset Document identified by docname {
o String docname
o String doctype
o String hash
o String from
o String to
}
transaction UploadDoc {
--> Document document
}
transaction ShareDoc {
--> Document document
--> Bank bank
}
transaction SetupDemo {
}
Script File :
/**
*
* #param {com.biz.UploadDoc} uploadDoc
* #transaction
*/
async function uploadDoc(uploadDoc) {
uploadDoc.document.docname = 'BC1.jpg';
uploadDoc.document.doctype = 'BC';
uploadDoc.document.hash = '123456';
uploadDoc.document.from = 'Bank_1';
uploadDoc.document.to = 'User_1';
const ar = await getAssetRegistry('com.biz.Document');
await ar.update(uploadDoc.document);
}
/**
*
* #param {com.biz.ShareDoc} shareDoc
* #transaction
*/
async function shareDoc(shareDoc) {
shareDoc.document.docname = 'BC1.jpg';
shareDoc.document.doctype = 'BC';
shareDoc.document.hash = '12346';
shareDoc.document.from = 'User_1';
shareDoc.document.to = shareDoc.bank.name;
const dr = await getAssetRegistry('com.biz.Document');
await dr.update(shareDoc.document);
}
/**
*
* #param {com.biz.SetupDemo} setupDemo
* #transaction
*/
async function setupDemo(setupDemo) {
const factory = getFactory();
const NS = 'com.biz';
const user = factory.newResource(NS,'User','User_1')
user.name='User_1';
const banks = [
factory.newResource(NS,'Bank','Bank_1'),
factory.newResource(NS,'Bank','Bank_2')
];
banks[0].name = 'Bank_1';
banks[1].name = 'Bank_2';
banks[0].points = 100;
banks[1].points = 100;
const userRegistry = await getParticipantRegistry(NS + '.User');
await userRegistry.addAll([user]);
const bankRegistry = await getParticipantRegistry(NS + '.Bank');
await bankRegistry.addAll(banks);
const documents = [
factory.newResource(NS, 'Document', 'Pa60.jpg'),
factory.newResource(NS, 'Document', 'Pa80.jpg')
];
documents[0].docname = 'Pa60.jpg';
documents[0].doctype = 'DrivingLicense';
documents[0].hash = '12345';
documents[0].from ='Bank_1';
documents[0].to = 'User_1';
documents[1].docname = 'Pa80.jpg';
documents[1].doctype = 'DrivingLicense';
documents[1].hash = '123456';
documents[1].from ='Bank_1';
documents[1].to = 'User_1';
const docRegistry = await getAssetRegistry(NS + '.Document')
await docRegistry.addAll(documents);
}
Issue : First I run transaction SetupDemo. Runs fine.
Next, I run transaction UploadDoc. Runs fine. Pa60.jpg is updated as BC1.jpg. ID given in Plyground for submitting transaction : Pa60.jpg
Next, I want to run ShareDoc transaction. If I give ID as BC1.jpg while submitting transaction, 'Object with id 'BC1.jpg' not found' error.
Please help me resolve the issue.
I have an Account object, which has many Transactions related to it.
In one method, I get all transactions for a particular account.
var transactionlines = (from p in Context.account_transaction
.Include("account_transaction_line")
// .Include("Account")
.Include("account.z_account_type")
.Include("account.institution")
.Include("third_party")
.Include("third_party.z_third_party_type")
.Include("z_account_transaction_type")
.Include("account_transaction_line.transaction_sub_category")
.Include("account_transaction_line.transaction_sub_category.transaction_category")
.Include("z_account_transaction_entry_type")
.Include("account_transaction_line.cost_centre")
where p.account_id == accountId
&& p.deleted == null
select p).ToList();
This is meant to return me a list of transactions, with their related objects. I then pass each object to a Translator, which translates them into data transfer objects, which are then passed back to my main application.
public TransactionDto TranslateTransaction(account_transaction source)
{
LogUserActivity("in TranslateTransaction");
var result = new TransactionDto
{
Id = source.id,
Version = source.version,
AccountId = source.account_id,
// Account = TranslateAccount(source.account, false),
ThirdPartyId = source.third_party_id,
ThirdParty = TranslateThirdParty(source.third_party),
Amount = source.transaction_amount,
EntryTypeId = source.account_transaction_entry_type_id,
EntryType = new ReferenceItemDto
{
Id = source.account_transaction_entry_type_id,
Description = source.z_account_transaction_entry_type.description,
Deleted = source.z_account_transaction_entry_type.deleted != null
},
Notes = source.notes,
TransactionDate = source.transaction_date,
TransactionTypeId = source.account_transaction_type_id,
TransactionType = new ReferenceItemDto
{
Id = source.z_account_transaction_type.id,
Description = source.z_account_transaction_type.description,
Deleted = source.z_account_transaction_type.deleted != null
}
};
... return my object
}
The problem is:
An account has Transactions, and a Transaction therefore belongs to an Account. It seems my translators are being called way too much, and reloading a lot of data because of this.
When I load my transaction object, it's 'account' property has a'transactions' propery, which has a list of all the transactions associated to that account. Each transaction then has an account property... and those account peroprties again, have a list of all the transactions... and on and on it goes.
Is there a way I can limit the loading to one level or something?
I have this set:
Context.Configuration.LazyLoadingEnabled = false;
I was hoping my 'Includes' would be all that is loaded... Don't load 'un-included' related data?
As requested, here is my TranslateAccount method:
public AccountDto TranslateAccount(account p, bool includeCardsInterestRateDataAndBalance)
{
LogUserActivity("in TranslateAccount");
if (p == null)
return null;
var result =
new AccountDto
{
Id = p.id,
Description = p.description,
PortfolioId = p.institution.account_portfolio_id,
AccountNumber = p.account_number,
Institution = TranslateInstitution(p.institution),
AccountType = new ReferenceItemDto
{
Id = p.account_type_id,
Description = p.z_account_type.description
},
AccountTypeId = p.account_type_id,
InstitutionId = p.institution_id,
MinimumBalance = p.min_balance,
OpeningBalance = p.opening_balance,
OpeningDate = p.opening_date
};
if (includeCardsInterestRateDataAndBalance)
{
// Add the assigned cards collection
foreach (var card in p.account_card)
{
result.Cards.Add(new AccountCardDto
{
Id = card.id,
AccountId = card.account_id,
Active = card.active,
CardHolderName = card.card_holder_name,
CardNumber = card.card_number,
ExpiryDate = card.expiry
});
}
// Populate the current interest rate
result.CurrentRate = GetCurrentInterestRate(result.Id);
// Add all rates to the account
foreach (var rate in p.account_rate)
{
result.Rates.Add(
new AccountRateDto
{
Id = rate.id,
Description = rate.description,
Deleted = rate.deleted != null,
AccountId = rate.account_id,
EndDate = rate.end_date,
Rate = rate.rate,
StartDate = rate.start_date
});
}
result.CurrentBalance = CurrentBalance(result.Id);
}
LogUserActivity("out TranslateAccount");
return result;
}
The entity framework context maintains a cache of data that has been pulled out of the database. Regardless of lazy loading being enabled/disabled, you can call Transaction.Account.Transactions[0].Account.Transactions[0]... as much as you want without loading anything else from the database.
The problem is not in the cyclical nature of entity framework objects - it is somewhere in the logic of your translation objects.