Skip to content

Commit 2158fe8

Browse files
authored
Merge branch 'master' into ptr-addr-u64
2 parents 8268126 + 4f1d875 commit 2158fe8

File tree

3 files changed

+314
-21
lines changed

3 files changed

+314
-21
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
2+
# Minting Nfts using yoroi backend and cardano-serialization-lib
3+
4+
This example mints nfts directly to an account on testnet.
5+
6+
code taken from (here)[https://github.com/ozgrakkurt/cardano-mint-nft]
7+
8+
```javascript
9+
import CardanoWasm from "@emurgo/cardano-serialization-lib-nodejs";
10+
import axios from "axios";
11+
import cbor from "cbor";
12+
13+
const mintNft = async (
14+
privateKey,
15+
policy,
16+
assetName,
17+
description,
18+
imageUrl,
19+
mediaType
20+
) => {
21+
const FEE = 300000;
22+
23+
const publicKey = privateKey.to_public();
24+
25+
const addr = CardanoWasm.BaseAddress.new(
26+
CardanoWasm.NetworkInfo.testnet().network_id(),
27+
CardanoWasm.StakeCredential.from_keyhash(publicKey.hash()),
28+
CardanoWasm.StakeCredential.from_keyhash(publicKey.hash())
29+
).to_address();
30+
31+
const policyPubKey = policy.privateKey.to_public();
32+
33+
const policyAddr = CardanoWasm.BaseAddress.new(
34+
CardanoWasm.NetworkInfo.testnet().network_id(),
35+
CardanoWasm.StakeCredential.from_keyhash(policyPubKey.hash()),
36+
CardanoWasm.StakeCredential.from_keyhash(policyPubKey.hash())
37+
).to_address();
38+
39+
console.log(`ADDR: ${addr.to_bech32()}`);
40+
41+
// get utxos for our address and select one that is probably big enough to pay the tx fee
42+
const utxoRes = await axios.post(
43+
"https://testnet-backend.yoroiwallet.com/api/txs/utxoForAddresses",
44+
{
45+
addresses: [addr.to_bech32()],
46+
}
47+
);
48+
49+
let utxo = null;
50+
51+
if (utxoRes.data) {
52+
for (const utxoEntry of utxoRes.data) {
53+
if (utxoEntry.amount > FEE) {
54+
utxo = utxoEntry;
55+
}
56+
}
57+
}
58+
59+
if (utxo === null) {
60+
throw new Error("no utxo found with sufficient ADA.");
61+
}
62+
63+
console.log(`UTXO: ${JSON.stringify(utxo, null, 4)}`);
64+
65+
// get current global slot from yoroi backend
66+
const { data: slotData } = await axios.get(
67+
"https://testnet-backend.yoroiwallet.com/api/v2/bestblock"
68+
);
69+
70+
const ttl = slotData.globalSlot + 60 * 60 * 2; // two hours from now
71+
72+
const txBuilder = CardanoWasm.TransactionBuilder.new(
73+
CardanoWasm.TransactionBuilderConfigBuilder.new()
74+
.fee_algo(
75+
CardanoWasm.LinearFee.new(
76+
CardanoWasm.BigNum.from_str("44"),
77+
CardanoWasm.BigNum.from_str("155381")
78+
)
79+
)
80+
.coins_per_utxo_word(CardanoWasm.BigNum.from_str("34482"))
81+
.pool_deposit(CardanoWasm.BigNum.from_str("500000000"))
82+
.key_deposit(CardanoWasm.BigNum.from_str("2000000"))
83+
.max_value_size(5000)
84+
.max_tx_size(16384)
85+
.build()
86+
);
87+
88+
const scripts = CardanoWasm.NativeScripts.new();
89+
90+
const policyKeyHash = CardanoWasm.BaseAddress.from_address(policyAddr)
91+
.payment_cred()
92+
.to_keyhash();
93+
94+
console.log(
95+
`POLICY_KEYHASH: ${Buffer.from(policyKeyHash.to_bytes()).toString("hex")}`
96+
);
97+
98+
// add key hash script so only people with policy key can mint assets using this policyId
99+
const keyHashScript = CardanoWasm.NativeScript.new_script_pubkey(
100+
CardanoWasm.ScriptPubkey.new(policyKeyHash)
101+
);
102+
scripts.add(keyHashScript);
103+
104+
const policyTtl = policy.ttl || ttl;
105+
106+
console.log(`POLICY_TTL: ${policyTtl}`);
107+
108+
// add timelock so policy is locked after this slot
109+
const timelock = CardanoWasm.TimelockExpiry.new(policyTtl);
110+
const timelockScript = CardanoWasm.NativeScript.new_timelock_expiry(timelock);
111+
scripts.add(timelockScript);
112+
113+
const mintScript = CardanoWasm.NativeScript.new_script_all(
114+
CardanoWasm.ScriptAll.new(scripts)
115+
);
116+
117+
const privKeyHash = CardanoWasm.BaseAddress.from_address(addr)
118+
.payment_cred()
119+
.to_keyhash();
120+
txBuilder.add_key_input(
121+
privKeyHash,
122+
CardanoWasm.TransactionInput.new(
123+
CardanoWasm.TransactionHash.from_bytes(Buffer.from(utxo.tx_hash, "hex")),
124+
utxo.tx_index
125+
),
126+
CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(utxo.amount))
127+
);
128+
129+
txBuilder.add_mint_asset_and_output_min_required_coin(
130+
mintScript,
131+
CardanoWasm.AssetName.new(Buffer.from(assetName)),
132+
CardanoWasm.Int.new_i32(1),
133+
CardanoWasm.TransactionOutputBuilder.new().with_address(addr).next()
134+
);
135+
136+
const policyId = Buffer.from(mintScript.hash().to_bytes()).toString("hex");
137+
138+
console.log(`POLICY_ID: ${policyId}`);
139+
140+
const metadata = {
141+
[policyId]: {
142+
[assetName]: {
143+
name: assetName,
144+
description,
145+
image: imageUrl,
146+
mediaType,
147+
},
148+
},
149+
};
150+
151+
console.log(`METADATA: ${JSON.stringify(metadata, null, 4)}`);
152+
153+
// transaction ttl can't be later than policy ttl
154+
const txTtl = ttl > policyTtl ? policyTtl : ttl;
155+
156+
console.log(`TX_TTL: ${txTtl}`);
157+
158+
txBuilder.set_ttl(txTtl);
159+
txBuilder.add_json_metadatum(
160+
CardanoWasm.BigNum.from_str("721"),
161+
JSON.stringify(metadata)
162+
);
163+
164+
txBuilder.add_change_if_needed(addr);
165+
166+
const txBody = txBuilder.build();
167+
const txHash = CardanoWasm.hash_transaction(txBody);
168+
169+
console.log(`TX_HASH: ${Buffer.from(txHash.to_bytes()).toString("hex")}`);
170+
171+
// sign the tx using the policy key and main key
172+
const witnesses = CardanoWasm.TransactionWitnessSet.new();
173+
const vkeyWitnesses = CardanoWasm.Vkeywitnesses.new();
174+
vkeyWitnesses.add(CardanoWasm.make_vkey_witness(txHash, policy.privateKey));
175+
vkeyWitnesses.add(CardanoWasm.make_vkey_witness(txHash, privateKey));
176+
witnesses.set_vkeys(vkeyWitnesses);
177+
witnesses.set_native_scripts;
178+
const witnessScripts = CardanoWasm.NativeScripts.new();
179+
witnessScripts.add(mintScript);
180+
witnesses.set_native_scripts(witnessScripts);
181+
182+
const unsignedTx = txBuilder.build_tx();
183+
184+
// create signed transaction
185+
const tx = CardanoWasm.Transaction.new(
186+
unsignedTx.body(),
187+
witnesses,
188+
unsignedTx.auxiliary_data()
189+
);
190+
191+
const signedTx = Buffer.from(tx.to_bytes()).toString("base64");
192+
193+
// submit the transaction using yoroi backend
194+
try {
195+
const { data } = await axios.post(
196+
"https://testnet-backend.yoroiwallet.com/api/txs/signed",
197+
{
198+
signedTx,
199+
}
200+
);
201+
202+
console.log(`SUBMIT_RESULT: ${JSON.stringify(data, null, 4)}`);
203+
} catch (error) {
204+
console.error(
205+
`failed to submit tx via yoroi backend: ${error.toString()}. error details: ${JSON.stringify(
206+
error.response?.data
207+
)}`
208+
);
209+
}
210+
};
211+
212+
try {
213+
const privateKey = CardanoWasm.PrivateKey.from_bech32(
214+
//"ed25519_sk1fde2u8u2qme8uau5ac3w6c082gvtnmxt6uke2w8e07xwzewxee3q3n0f8e"
215+
"ed25519_sk18j0a6704zyerm6dsj6p2fp8juw5m43rfgk0y84jnm7w5khs4dpqquewh43"
216+
);
217+
218+
console.log(`PRIVATE KEY: ${privateKey.to_bech32()}`);
219+
220+
/*
221+
const policyPrivateKey = CardanoWasm.PrivateKey.from_bech32(
222+
"ed25519_sk1q96x2g66j5g7u5wydl7kcagk0h8upxznt3gj48h6njqthkyr7faqxmnnte"
223+
);
224+
*/
225+
226+
// import policy key from a .skey file
227+
const policyPrivateKey = CardanoWasm.PrivateKey.from_normal_bytes(
228+
cbor.decodeFirstSync(
229+
"582009ca7f508dd5a5f9823d367e98170f25606799f49ae7363a47a11d7d3502c91f"
230+
)
231+
);
232+
233+
console.log(`POLICY_PRIV_KEY: ${policyPrivateKey.to_bech32()}`);
234+
235+
await mintNft(
236+
privateKey, // main key
237+
{
238+
privateKey: policyPrivateKey, // policy key
239+
// pass null here to get automatic ttl for policy
240+
// and paste the POLICY_TTL output you get in console to here to mint with same policy
241+
ttl: null, // policy ttl
242+
},
243+
"asdNFT5", // assetName
244+
"some descr this is a new nft with same policy", // description
245+
"ipfs://QmNhmDPJMgdsFRM9HyiQEJqrKkpsWFshqES8mPaiFRq9Zk", // image url
246+
"image/jpeg" // mediaType
247+
);
248+
} catch (err) {
249+
console.error(`failed to mint nft: ${err.toString()}`);
250+
}
251+
```

rust/src/tx_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,7 @@ impl TransactionBuilder {
12321232
}
12331233
}
12341234
}
1235-
None => Err(JsError::from_str("missing input for some native asset")),
1235+
None => Err(JsError::from_str("missing input or output for some native asset")),
12361236
}
12371237
}
12381238

rust/src/utils.rs

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,8 @@ impl Deserialize for Value {
479479
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
480480
pub struct Int(pub (crate) i128);
481481

482+
to_from_bytes!(Int);
483+
482484
#[wasm_bindgen]
483485
impl Int {
484486
pub fn new(x: &BigNum) -> Self {
@@ -573,13 +575,30 @@ impl Deserialize for Int {
573575
(|| -> Result<_, DeserializeError> {
574576
match raw.cbor_type()? {
575577
cbor_event::Type::UnsignedInteger => Ok(Self(raw.unsigned_integer()? as i128)),
576-
cbor_event::Type::NegativeInteger => Ok(Self(raw.negative_integer()? as i128)),
578+
cbor_event::Type::NegativeInteger => Ok(Self(read_nint(raw)?)),
577579
_ => Err(DeserializeFailure::NoVariantMatched.into()),
578580
}
579581
})().map_err(|e| e.annotate("Int"))
580582
}
581583
}
582584

585+
/// TODO: this function can be removed in case `cbor_event` library ever gets a fix on their side
586+
/// See https://github.com/Emurgo/cardano-serialization-lib/pull/392
587+
fn read_nint<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result <i128, DeserializeError> {
588+
let found = raw.cbor_type()?;
589+
if found != cbor_event::Type::NegativeInteger {
590+
return Err(cbor_event::Error::Expected(cbor_event::Type::NegativeInteger, found).into());
591+
}
592+
let (len, len_sz) = raw.cbor_len()?;
593+
match len {
594+
cbor_event::Len::Indefinite => Err(cbor_event::Error::IndefiniteLenNotSupported(cbor_event::Type::NegativeInteger).into()),
595+
cbor_event::Len::Len(v) => {
596+
raw.advance(1 + len_sz)?;
597+
Ok(-(v as i128) - 1)
598+
}
599+
}
600+
}
601+
583602
const BOUNDED_BYTES_CHUNK_SIZE: usize = 64;
584603

585604
pub (crate) fn write_bounded_bytes<'se, W: Write>(serializer: &'se mut Serializer<W>, bytes: &[u8]) -> cbor_event::Result<&'se mut Serializer<W>> {
@@ -706,22 +725,28 @@ impl cbor_event::se::Serialize for BigInt {
706725
num_bigint::Sign::Minus => serializer.write_negative_integer(-(*u64_digits.first().unwrap() as i128) as i64),
707726
},
708727
_ => {
728+
// Small edge case: nint's minimum is -18446744073709551616 but in this bigint lib
729+
// that takes 2 u64 bytes so we put that as a special case here:
730+
if sign == num_bigint::Sign::Minus && u64_digits == vec![0, 1] {
731+
serializer.write_negative_integer(-18446744073709551616i128 as i64)
732+
} else {
709733
let (sign, bytes) = self.0.to_bytes_be();
710-
match sign {
711-
// positive bigint
712-
num_bigint::Sign::Plus |
713-
num_bigint::Sign::NoSign => {
714-
serializer.write_tag(2u64)?;
715-
write_bounded_bytes(serializer, &bytes)
716-
},
717-
// negative bigint
718-
num_bigint::Sign::Minus => {
719-
serializer.write_tag(3u64)?;
720-
use std::ops::Neg;
721-
// CBOR RFC defines this as the bytes of -n -1
722-
let adjusted = self.0.clone().neg().checked_sub(&num_bigint::BigInt::from(1u32)).unwrap().to_biguint().unwrap();
723-
write_bounded_bytes(serializer, &adjusted.to_bytes_be())
724-
},
734+
match sign {
735+
// positive bigint
736+
num_bigint::Sign::Plus |
737+
num_bigint::Sign::NoSign => {
738+
serializer.write_tag(2u64)?;
739+
write_bounded_bytes(serializer, &bytes)
740+
},
741+
// negative bigint
742+
num_bigint::Sign::Minus => {
743+
serializer.write_tag(3u64)?;
744+
use std::ops::Neg;
745+
// CBOR RFC defines this as the bytes of -n -1
746+
let adjusted = self.0.clone().neg().checked_sub(&num_bigint::BigInt::from(1u32)).unwrap().to_biguint().unwrap();
747+
write_bounded_bytes(serializer, &adjusted.to_bytes_be())
748+
},
749+
}
725750
}
726751
},
727752
}
@@ -753,7 +778,7 @@ impl Deserialize for BigInt {
753778
// uint
754779
CBORType::UnsignedInteger => Ok(Self(num_bigint::BigInt::from(raw.unsigned_integer()?))),
755780
// nint
756-
CBORType::NegativeInteger => Ok(Self(num_bigint::BigInt::from(raw.negative_integer()?))),
781+
CBORType::NegativeInteger => Ok(Self(num_bigint::BigInt::from(read_nint(raw)?))),
757782
_ => return Err(DeserializeFailure::NoVariantMatched.into()),
758783
}
759784
})().map_err(|e| e.annotate("BigInt"))
@@ -2085,9 +2110,8 @@ mod tests {
20852110
assert_eq!(hex::decode("c249010000000000000000").unwrap(), BigInt::from_str("18446744073709551616").unwrap().to_bytes());
20862111
// uint
20872112
assert_eq!(hex::decode("1b000000e8d4a51000").unwrap(), BigInt::from_str("1000000000000").unwrap().to_bytes());
2088-
// nint
2089-
// we can't use this due to cbor_event actually not supporting the full NINT spectrum as it uses an i64 for some reason...
2090-
//assert_eq!(hex::decode("3bffffffffffffffff").unwrap(), BigInt::from_str("-18446744073709551616").unwrap().to_bytes());
2113+
// nint (lowest possible - used to be unsupported but works now)
2114+
assert_eq!(hex::decode("3bffffffffffffffff").unwrap(), BigInt::from_str("-18446744073709551616").unwrap().to_bytes());
20912115
// this one fits in an i64 though
20922116
assert_eq!(hex::decode("3903e7").unwrap(), BigInt::from_str("-1000").unwrap().to_bytes());
20932117

@@ -2299,4 +2323,22 @@ mod tests {
22992323
assert_eq!(Int::new_i32(42).as_i32_or_fail().unwrap(), 42);
23002324
assert_eq!(Int::new_i32(-42).as_i32_or_fail().unwrap(), -42);
23012325
}
2326+
2327+
#[test]
2328+
fn int_full_range() {
2329+
// cbor_event's nint API worked via i64 but we now have a workaround for it
2330+
// so these tests are here to make sure that workaround works.
2331+
2332+
// first nint below of i64::MIN
2333+
let bytes_x = vec![0x3b, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
2334+
let x = Int::from_bytes(bytes_x.clone()).unwrap();
2335+
assert_eq!(x.to_str(), "-9223372036854775809");
2336+
assert_eq!(bytes_x, x.to_bytes());
2337+
2338+
// smallest possible nint which is -u64::MAX - 1
2339+
let bytes_y = vec![0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
2340+
let y = Int::from_bytes(bytes_y.clone()).unwrap();
2341+
assert_eq!(y.to_str(), "-18446744073709551616");
2342+
assert_eq!(bytes_y, y.to_bytes());
2343+
}
23022344
}

0 commit comments

Comments
 (0)