I am trying to build a web3 based e-commerce site using Anchor.
I've just started learning about PDAs and there's a error I've been getting for hours, like the one in the title.
My contract:
#[program]
pub mod dailsap_store_contract {
use super::*;
pub fn create_collection(
ctx: Context<CreateCollection>,
name: String,
description: String,
image_uri: String,
) -> Result<()> {
let collection: &mut Account<Collection> = &mut ctx.accounts.collection;
let authority: &Signer = &ctx.accounts.authority;
let clock: Clock = Clock::get().unwrap();
let bump = *ctx.bumps.get("collection").unwrap();
if name.chars().count() > 50 {
return Err(ErrorCode::CollectionNameTooLong.into());
}
if description.chars().count() > 250 {
return Err(ErrorCode::CollectionDescriptionTooLong.into());
}
if image_uri.chars().count() > 60 {
return Err(ErrorCode::CollectionImageUrlTooLong.into());
}
collection.authority = *authority.key;
collection.timestamp = clock.unix_timestamp;
collection.name = name;
collection.description = description;
collection.image = image_uri;
collection.bump = bump;
Ok(())
}
pub fn update_collection(
ctx: Context<UpdateCollection>,
name: String,
description: String,
image_uri: String,
) -> Result<()> {
let base_collection: &mut Account<Collection> = &mut ctx.accounts.collection_account;
base_collection.name = name;
base_collection.description = description;
base_collection.image = image_uri;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateCollection<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(init, payer=authority, space = Collection::LEN, seeds=[b"collection", collection.key().as_ref()], bump)]
pub collection: Account<'info, Collection>,
#[account(address = system_program::ID)]
pub system_program: Program<'info, System>,
}
#[account]
pub struct Collection {
authority: Pubkey,
timestamp: i64,
name: String,
description: String,
image: String,
bump: u8,
}
Frontend:
const collection = anchor.web3.Keypair.generate();
const [collectionPDA, _] = await anchor.web3.PublicKey.findProgramAddress(
[
anchor.utils.bytes.utf8.encode("collection"),
collection.publicKey.toBuffer(),
],
program.programId
);
await program.methods
.createCollection(
"This is collection name",
"This is collection description",
"Hello World"
)
.accounts({
collection: collectionPDA,
authority: anchor.AnchorProvider.env().publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
The problem should be here: seeds=[b"collection", collection.key().as_ref()]
The source from which I received help: https://book.anchor-lang.com/anchor_in_depth/PDAs.html
But I'm getting errors in a way I don't understand
can you help?
To be honest, I'm surprised the program got so far! The program-derived address is meant to be derived from a set of seeds and the program id, and in your case, the collection address is using itself as a seed, which would normally mean infinite recursion. But in this case, it only goes down one level and fails to derive itself.
Try any other seeds on your collection address, even something like:
#[account(init, payer=authority, space = Collection::LEN, seeds=[b"collection", authority.key().as_ref()], bump)]
pub collection: Account<'info, Collection>,
I came up with a solution like this, I'm not sure how logical it is.
...
#[program]
pub mod dailsap_store_contract {
use super::*;
pub fn create_collection(
ctx: Context<CreateCollection>,
id: Pubkey,
name: String,
description: String,
image_uri: String,
) -> Result<()> {
let collection: &mut Account<Collection> = &mut ctx.accounts.collection;
let authority: &Signer = &ctx.accounts.authority;
let clock: Clock = Clock::get().unwrap();
let bump = *ctx.bumps.get("collection").unwrap();
if name.chars().count() > 50 {
return Err(ErrorCode::CollectionNameTooLong.into());
}
...
#[derive(Accounts)]
#[instruction(id: Pubkey)]
pub struct CreateCollection<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(init, payer=authority, space = Collection::LEN, seeds=[b"collection", id.as_ref()], bump)]
pub collection: Account<'info, Collection>,
#[account(address = system_program::ID)]
pub system_program: Program<'info, System>,
}
#[account]
pub struct Collection {
id: Pubkey,
authority: Pubkey,
timestamp: i64,
name: String,
description: String,
image: String,
bump: u8,
}
...
Frontend:
const collection = anchor.web3.Keypair.generate();
const [collectionPDA, _] = await anchor.web3.PublicKey.findProgramAddress(
[
Buffer.from(anchor.utils.bytes.utf8.encode("collection")),
collection.publicKey.toBuffer(),
],
program.programId
);
await program.methods
.createCollection(
collection.publicKey,
"This is collection name",
"This is collection description",
"Hello World"
)
.accounts({
collection: collectionPDA,
authority: anchor.AnchorProvider.env().publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
I was trying to create a function to make a GET with query parameters. I was dealing with the Mangadex API and was to send a parameter called 'manga' as an array. I created the code as follows:
Future<http.Response> getCoverArtResponse(String mangaID) async {
var queryParameters = {
'limit': '10',
'manga': [mangaID] //Here
};
var unencodedPath = '/cover';
var response = await http.get(
Uri.https(authority, unencodedPath, queryParameters),
headers: {HttpHeaders.contentTypeHeader: 'application/json'});
return response;
}
However, the response was the following error:
{"result":"error","errors":[{"id":"9c346772-7b14-5982-b4b6-7b5888522762","status":400,"title":"validation_exception","detail":"Error validating \/manga: String value found, but an array is required","context":null}]}
How am I supposed to send the parameters? So far I have tried -
'manga': [mangaID]
'manga': '[$mangaID]'
None of them seem to work.
import 'dart:async';
import 'package:wnetworking/wnetworking.dart';
class MangaDex {
static const _base = 'https://api.mangadex.org';
static FutureOr<void> _getter({required String url, required Function(JMap item, int idx) onItem}) async {
await HttpReqService.getJson<JMap>(url)
.then((response) {
var results = response?['results'];
if (results != null) {
if (results is List) {
var i = 0;
results.forEach((manga) => onItem(manga, ++i));
} else {
print(response);
}
}
});
}
static FutureOr<void> cover({int limit = 10, int offset=0, String? mangaId, String? coverId}) async {
final mangas = mangaId != null ? '&manga[]=$mangaId' : '';
final covers = coverId != null ? '&ids[]=$coverId' : '';
final url = '$_base/cover?limit=$limit&offset=$offset$mangas$covers';
await _getter(
url: url,
onItem: (item, idx) {
print('$idx) "${item['data']?['attributes']?['fileName']}"');
print(' id: ${item['data']?['id']}\n');
},
);
}
}
void main(List<String> args) async {
await MangaDex.cover(mangaId: '32d76d19-8a05-4db0-9fc2-e0b0648fe9d0', limit: 2);
print('\nJob done');
}
Result:
1) "f5873770-80a4-470e-a11c-63b709d87eb3.jpg"
id: b6c7ce9c-e671-4f26-90b0-e592188e9cd6
2) "e9f926db-b469-48c4-8cc4-a8e523ad75ca.jpg"
id: 00aae6e0-46bb-4f92-a82a-1c740789b704
Job done
Replace wnetworking package with http package, and JMap with Map<String, dynamic>
NOTE: MangaDex Documentation is lacking and misleading about how to correctly use its endpoints.
I wanted to convert a string to map.
String value = "{first_name : fname,last_name : lname,gender : male, location : { state : state, country : country, place : place} }"
into
Map = {
first_name : fname,
last_name : lname,
gender : male,
location = {
state : state,
country : country,
place : place
}
}
How do I convert the string into a map<String, dynamic> where the value consists of string, int, object, and boolean?
I wanted to save the string to a file and obtain the data from the file.
That's not possible.
If you can change the string to valid JSON, you can use
import 'dart:convert';
...
Map valueMap = json.decode(value);
// or
Map valueMap = jsonDecode(value);
The string would need to look like
{"first_name" : "fname","last_name" : "lname","gender" : "male", "location" : { "state" : "state", "country" : "country", "place" : "place"} }
You would have to change the way you create the string.
I'm guessing you are creating the string using the yourMap.toString() method. You should rather use json.encode(yourMap), which converts your map to valid JSON, which you can the parse with json.decode(yourString).
create two objects
class User {
final String firstName;
final String lastName;
final String gender;
final location;
User({
this.firstName,
this.lastName,
this.gender,
this.location,
});
User.fromJson(Map json)
: firstName = json['firstName'],
lastName = json['lastName'],
gender = json['gender'],
location = Location.fromJson(json['location']);
}
class Location {
final String state;
final String country;
final String place;
Location({
this.state,
this.country,
this.place,
});
Location.fromJson(Map json)
: state = json['state'],
country = json['country'],
place = json['place'];
}
then use it like this
var user = User.fromJson(value);
print(user.firstName);
or convert it to list like this
var user = User.fromJson(value).toList();
you can do like this ->
import 'dart:convert';
...
if your data like this **
{'bus1':'100Tk','bus2':'150TK','bus3':'200TK'}
**;
then you can do like this ->
Map valueMap = json.decode(value);
// or
Map valueMap = jsonDecode(value);
or if like this ->var data = {'1':'100TK','2':'200TK','3':'300TK'};
var dataSp = data.split(',');
Map<String,String> mapData = Map();
dataSp.forEach((element) => mapData[element.split(':')[0]] = element.split(':')[1]);
Note: Map first value was Int that's why I did that.
Make a wrapper class for the location where you define the methods fromMap, toMap
Yeah, that's not possible.
But i have workaround to fix that.
Remove space in ur invalid json
Fix ur invalid string json to valid string json
Convert valid string json to map
Here's the full code for above process:
import 'dart:convert';
void main() {
String value = "{first_name : fname,last_name : lname,gender : male, location : { state : state, country : country, place : place} }";
String jsonString = _convertToJsonStringQuotes(raw: value);
print("Test 1: $jsonString");
final Map<dynamic, dynamic> result = json.decode(jsonString);
print('Test 2: $result');
}
String _convertToJsonStringQuotes({required String raw}) {
/// remove space
String jsonString = raw.replaceAll(" ", "");
/// add quotes to json string
jsonString = jsonString.replaceAll('{', '{"');
jsonString = jsonString.replaceAll(':', '": "');
jsonString = jsonString.replaceAll(',', '", "');
jsonString = jsonString.replaceAll('}', '"}');
/// remove quotes on object json string
jsonString = jsonString.replaceAll('"{"', '{"');
jsonString = jsonString.replaceAll('"}"', '"}');
/// remove quotes on array json string
jsonString = jsonString.replaceAll('"[{', '[{');
jsonString = jsonString.replaceAll('}]"', '}]');
return jsonString;
}
To convert a string into a map<String, dynamic>, you can use the
following code:
String value = "{first_name : fname,last_name : lname,gender : male, location : { state : state, country : country, place : place} }";
String result = value
.replaceAll("{","{\"")
.replaceAll("}","\"}")
.replaceAll(":","\":\"")
.replaceAll(",","\",\"");
print(result);
Here, we first replace the opening and closing curly braces with double quotes, and then replace the colons and commas with quotes to create a valid JSON string. Then, we use the jsonDecode method to convert the JSON string into a map.
I found a way to cast that string
Ok, lets use a complex model to cast:
final testMap = {
'userName': 'Igor',
'age': 22,
'totalCash': 138.57,
'isMale:': true,
'userStatus': {
'isUserActive': true,
'isAPremiumUser': false,
},
'userTags': ['Flutter Developer', 'Proactive', 'Clean code'],
'userCourses': [
{
'title': 'How to use TDD in flutter',
'finished': false,
'coursePercentage': 47.4,
'buyDate': '1969-07-20T20:18:04.000Z',
'courseTag': ['New', 'Popular'],
'courseDetails': null,
},
{
'title': 'Clean arquiteture in flutter',
'finished': false,
'coursePercentage': 20.8,
'buyDate': '1969-07-20T20:18:04.000Z',
'courseTag': ['New'],
'courseDetails': {
'teacherName': 'Tayler Mostoult',
'totalSubscribers': 5402,
},
},
{
'title': 'Micro-frontends in flutter',
'finished': true,
'coursePercentage': 100.0,
'buyDate': '1969-07-20T20:18:04.000Z',
'courseTag': [],
'courseDetails': {},
},
]
};
Know, cast it to string:
final testMapInStringFormat = testMap.toString();
To convert this String to map, we can use:
final String response = _getJsonFromString(testMap.toString());
final Map jsonConvertido = jsonDecode(response); // Decoded, back to map format
The function that will effectively do the casting:
String _getJsonFromString(String rawText) {
// Will find, for exemple, the text: "{isUserActive:"
final regexMapKeyWithOpenBracket = RegExp('(?<={)(.*?):+');
// Will find, for exemple, the text: ", userCourses:"
final regexMapKeyWithCommaAndSpace = RegExp(r'(?<=, )([^\]]*?):');
final regexOnlyKeyInLine = RegExp(r'^.+:$');
final splitedSentences = rawText
.replaceAllMapped(regexMapKeyWithCommaAndSpace,
(Match match) => '\n${match.text.trim()}\n')
.replaceAllMapped(regexMapKeyWithOpenBracket,
(Match match) => '\n${match.text.trim()}\n')
.replaceAll(RegExp(r'}(?=,|]|}|$|\s+)'), '\n}\n')
.replaceAll(RegExp(r'(?<=(,|:|^|\[)\s?){'), '\n{\n')
.replaceAll(RegExp('\\[\\s?\\]'), '\n[\n]\n')
.replaceAll(RegExp('\\{\\s?\\}'), '\n{\n}\n')
.split('\n')
..removeWhere((element) => element.replaceAll(' ', '').isEmpty);
final List<String> correctLines = [];
for (String line in splitedSentences) {
final isMapKey = regexOnlyKeyInLine.hasMatch(line);
if (isMapKey) {
final lineWithoutFinalTwoDots = line.substring(0, line.length - 1);
final lineWithQuaot = _putQuotationMarks(lineWithoutFinalTwoDots);
correctLines.add('$lineWithQuaot:');
} else {
String l = line.trim();
// If it falls in this else, it is a value of a key or a map structure
final isNumber = double.tryParse(l) != null || int.tryParse(l) != null;
final isBolean = l == 'false' || l == 'true';
final isStructureCaracter = ['{', '}', '[', ']', ','].any((e) => e == l);
final isNull = l == 'null';
if (isStructureCaracter || isNumber || isBolean || isNull) {
correctLines.add(l);
continue;
}
final hasCommaInFinal = l.endsWith(',');
if (hasCommaInFinal) {
l = l.substring(0, l.length - 1);
}
// If you got to this point, i'm sure it's a value string, so lets add a double quote
final lineWithQuaot = _putQuotationMarks(l);
if (hasCommaInFinal) {
correctLines.add('$lineWithQuaot,');
} else {
correctLines.add(lineWithQuaot);
}
}
}
return correctLines.join('');
}
extension MatchExtension on Match {
String get text => input.substring(start, end);
}
String _putQuotationMarks(String findedText) {
if (!findedText.startsWith('\'') && !findedText.startsWith('"')) {
findedText = findedText[0] + findedText.substring(1);
}
if (!findedText.endsWith('\'')) {
final lastIndex = findedText.length - 1;
findedText = findedText.substring(0, lastIndex) + findedText[lastIndex];
}
return '"$findedText"';
}
Use below method
just pass String json data it will give Map data
jsonStringToMap(String data){
List<String> str = data.replaceAll("{","").replaceAll("}","").replaceAll("\"","").replaceAll("'","").split(",");
Map<String,dynamic> result = {};
for(int i=0;i<str.length;i++){
List<String> s = str[i].split(":");
result.putIfAbsent(s[0].trim(), () => s[1].trim());
}
return result;
}