@minatokens/storage
Version:
MinaTokens Storage Library
736 lines (723 loc) • 25.2 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// dist/node/index.js
var index_exports = {};
__export(index_exports, {
FieldOption: () => FieldOption,
OffChainList: () => OffChainList,
OffChainListBase: () => OffChainListBase,
OffchainMap: () => OffchainMap,
OffchainMapOption: () => OffchainMapOption,
Storage: () => Storage,
UInt64Option: () => UInt64Option,
Whitelist: () => Whitelist,
WhitelistedAddress: () => WhitelistedAddress,
bigintFromBase56: () => bigintFromBase56,
bigintFromBase64: () => bigintFromBase64,
bigintToBase56: () => bigintToBase56,
bigintToBase64: () => bigintToBase64,
createIpfsURL: () => createIpfsURL,
deserializeIndexedMerkleMap: () => deserializeIndexedMerkleMap,
fieldFromBase56: () => fieldFromBase56,
fieldFromBase64: () => fieldFromBase64,
fieldToBase56: () => fieldToBase56,
fieldToBase64: () => fieldToBase64,
fromBase: () => fromBase,
loadIndexedMerkleMap: () => loadIndexedMerkleMap,
parseIndexedMapSerialized: () => parseIndexedMapSerialized,
pinJSON: () => pinJSON,
saveIndexedMerkleMap: () => saveIndexedMerkleMap,
serializeIndexedMap: () => serializeIndexedMap,
sleep: () => sleep,
toBase: () => toBase
});
module.exports = __toCommonJS(index_exports);
// dist/node/base64/bigint.js
var TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
function bigintToBase56(value) {
const digits = toBase(value, 56n);
const str = digits.map((x) => TABLE[Number(x)]).join("");
return str;
}
function bigintFromBase56(str) {
const base56Digits = str.split("").map((x2) => BigInt(TABLE.indexOf(x2)));
const x = fromBase(base56Digits, 56n);
return x;
}
function bigintToBase64(value) {
const digits = toBase(value, 64n);
const str = digits.map((x) => TABLE[Number(x)]).join("");
return str;
}
function bigintFromBase64(str) {
const base64Digits = str.split("").map((x2) => BigInt(TABLE.indexOf(x2)));
const x = fromBase(base64Digits, 64n);
return x;
}
function fromBase(digits, base) {
if (base <= 0n)
throw Error("fromBase: base must be positive");
let basePowers = [];
for (let power = base, n = 1; n < digits.length; power **= 2n, n *= 2) {
basePowers.push(power);
}
let k = basePowers.length;
digits = digits.concat(Array(2 ** k - digits.length).fill(0n));
for (let i = 0; i < k; i++) {
let newDigits = Array(digits.length >> 1);
let basePower = basePowers[i];
for (let j = 0; j < newDigits.length; j++) {
newDigits[j] = digits[2 * j] + basePower * digits[2 * j + 1];
}
digits = newDigits;
}
console.assert(digits.length === 1);
let [digit] = digits;
return digit;
}
function toBase(x, base) {
if (base <= 0n)
throw Error("toBase: base must be positive");
let basePowers = [];
for (let power = base; power <= x; power **= 2n) {
basePowers.push(power);
}
let digits = [x];
let k = basePowers.length;
for (let i = 0; i < k; i++) {
let newDigits = Array(2 * digits.length);
let basePower = basePowers[k - 1 - i];
for (let j = 0; j < digits.length; j++) {
let x2 = digits[j];
let high = x2 / basePower;
newDigits[2 * j + 1] = high;
newDigits[2 * j] = x2 - high * basePower;
}
digits = newDigits;
}
while (digits[digits.length - 1] === 0n) {
digits.pop();
}
return digits;
}
// dist/node/base64/field.js
var import_o1js = require("o1js");
var TABLE2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
function fieldToBase56(field) {
const digits = toBase(field.toBigInt(), 56n);
const str = digits.map((x) => TABLE2[Number(x)]).join("");
return str;
}
function fieldFromBase56(str) {
const base56Digits = str.split("").map((x2) => BigInt(TABLE2.indexOf(x2)));
const x = fromBase(base56Digits, 56n);
return (0, import_o1js.Field)(x);
}
function fieldToBase64(field) {
const digits = toBase(field.toBigInt(), 64n);
const str = digits.map((x) => TABLE2[Number(x)]).join("");
return str;
}
function fieldFromBase64(str) {
const base64Digits = str.split("").map((x2) => BigInt(TABLE2.indexOf(x2)));
const x = fromBase(base64Digits, 64n);
return (0, import_o1js.Field)(x);
}
// dist/node/indexed-map/indexed-map.js
var import_o1js2 = require("o1js");
// dist/node/util/sleep.js
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// dist/node/storage/pinata.js
async function pinJSON(params) {
const { data, name = "data.json", keyvalues = { library: "zkcloudworker" } } = params;
const auth = params.auth ?? process.env.PINATA_JWT ?? process.env.NEXT_PUBLIC_PINATA_JWT ?? process.env.REACT_APP_PINATA_JWT;
if (!auth)
throw new Error("pinJSON: auth, PINATA_JWT, NEXT_PUBLIC_PINATA_JWT or REACT_APP_PINATA_JWT should be defined");
try {
const pinataData = {
pinataOptions: {
cidVersion: 1
},
pinataMetadata: {
name,
keyvalues
},
pinataContent: data
};
const res = await fetch("https://api.pinata.cloud/pinning/pinJSONToIPFS", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + auth
},
body: JSON.stringify(pinataData)
});
if (!res.ok) {
throw new Error(`Pinata error: status: ${res.status} ${res.statusText}`);
}
const responseData = await res.json();
console.log("saveToIPFS result:", responseData);
return responseData?.IpfsHash;
} catch (error) {
console.error("saveToIPFS error:", error?.message);
return void 0;
}
}
// dist/node/indexed-map/indexed-map.js
var { IndexedMerkleMap } = import_o1js2.Experimental;
async function loadIndexedMerkleMap(params) {
const { url, type, name = "indexed-map", timeout = 6e4, attempts = 5 } = params;
let attempt = 0;
const start = Date.now();
let response = await fetch(url);
while (!response.ok && attempt < attempts && Date.now() - start < timeout) {
attempt++;
await sleep(5e3 * attempt);
response = await fetch(url);
}
if (!response.ok) {
throw new Error("Failed to fetch IndexedMerkleMap");
}
const json = await response.json();
const serializedIndexedMap = json[name].map;
if (!serializedIndexedMap)
throw new Error("wrong IndexedMerkleMap json format");
const map = deserializeIndexedMerkleMapInternal({
serializedIndexedMap,
type
});
if (!map) {
throw new Error("Failed to deserialize whitelist");
}
return map;
}
async function saveIndexedMerkleMap(params) {
const { map, name = "indexed-map", keyvalues, auth, filename = "indexed-map" } = params;
const serialized = serializeIndexedMap(map);
const json = { [name]: { map: serialized } };
const ipfsHash = await pinJSON({
data: json,
name: filename,
keyvalues,
auth
});
return ipfsHash;
}
function serializeIndexedMap(map) {
return {
height: map.height,
root: map.root.toJSON(),
length: map.length.toJSON(),
nodes: JSON.stringify(map.data.get().nodes, (_, v) => typeof v === "bigint" ? "n" + bigintToBase64(v) : v),
sortedLeaves: JSON.stringify(map.data.get().sortedLeaves.map((v) => [
bigintToBase64(v.key),
bigintToBase64(v.nextKey),
bigintToBase64(v.value),
bigintToBase64(BigInt(v.index))
]))
};
}
function deserializeIndexedMerkleMap(params) {
try {
const { serializedIndexedMap, type } = params;
return deserializeIndexedMerkleMapInternal({
serializedIndexedMap,
type: type ?? IndexedMerkleMap(serializedIndexedMap.height)
});
} catch (error) {
console.error("Error deserializing map:", error?.message ?? error);
return void 0;
}
}
function parseIndexedMapSerialized(serializedMap) {
const json = JSON.parse(serializedMap);
if (json.height === void 0 || json.root === void 0 || json.length === void 0 || json.nodes === void 0 || json.sortedLeaves === void 0)
throw new Error("wrong IndexedMerkleMap json format");
if (typeof json.height !== "number")
throw new Error("wrong IndexedMerkleMap height format");
if (typeof json.root !== "string")
throw new Error("wrong IndexedMerkleMap root format");
if (typeof json.length !== "string")
throw new Error("wrong IndexedMerkleMap length format");
if (typeof json.nodes !== "string")
throw new Error("wrong IndexedMerkleMap nodes format");
if (typeof json.sortedLeaves !== "string")
throw new Error("wrong IndexedMerkleMap sortedLeaves format");
return json;
}
function deserializeIndexedMerkleMapInternal(params) {
const { serializedIndexedMap, type } = params;
const map = new type();
if (serializedIndexedMap.height !== map.height) {
throw new Error("wrong IndexedMap height");
}
const nodes = JSON.parse(serializedIndexedMap.nodes, (_, v) => {
if (typeof v === "string" && v[0] === "n") {
return bigintFromBase64(v.slice(1));
}
return v;
});
const sortedLeaves = JSON.parse(serializedIndexedMap.sortedLeaves).map((row) => {
return {
key: bigintFromBase64(row[0]),
nextKey: bigintFromBase64(row[1]),
value: bigintFromBase64(row[2]),
index: Number(bigintFromBase64(row[3]))
};
});
map.root = import_o1js2.Field.fromJSON(serializedIndexedMap.root);
map.length = import_o1js2.Field.fromJSON(serializedIndexedMap.length);
map.data.updateAsProver(() => {
return {
nodes: nodes.map((row) => [...row]),
sortedLeaves: [...sortedLeaves]
};
});
return map;
}
// dist/node/storage/ipfs.js
function createIpfsURL(params) {
let { hash, gateway, apiToken } = params;
gateway ??= process.env.PINATA_IPFS_GATEWAY ?? process.env.NEXT_PUBLIC_PINATA_IPFS_GATEWAY ?? process.env.REACT_APP_PINATA_IPFS_GATEWAY;
apiToken ??= process.env.PINATA_GATEWAY_TOKEN ?? process.env.NEXT_PUBLIC_PINATA_GATEWAY_TOKEN ?? process.env.REACT_APP_PINATA_GATEWAY_TOKEN;
if (!gateway) {
gateway = "https://gateway.pinata.cloud/ipfs/";
}
return gateway + hash + (apiToken ? "?pinataGatewayToken=" + apiToken : "");
}
// dist/node/storage/storage.js
var import_o1js3 = require("o1js");
var Storage = class _Storage extends (0, import_o1js3.Struct)({
url: import_o1js3.Provable.Array(import_o1js3.Field, 2)
}) {
constructor(value) {
super(value);
}
/**
* Asserts that two Storage instances are equal.
* @param a The first Storage instance.
* @param b The second Storage instance.
*/
static assertEquals(a, b) {
a.url[0].assertEquals(b.url[0]);
a.url[1].assertEquals(b.url[1]);
}
/**
* Checks if two Storage instances are equal.
* @param a The first Storage instance.
* @param b The second Storage instance.
* @returns A Bool indicating whether the two instances are equal.
*/
static equals(a, b) {
return a.url[0].equals(b.url[0]).and(a.url[1].equals(b.url[1]));
}
/**
* Creates a Storage instance from a string.
* @param url The string representing the storage URL.
* @returns A new Storage instance.
*/
static fromString(url) {
if (url === "")
return _Storage.empty();
const fields = import_o1js3.Encoding.stringToFields(url);
if (fields.length !== 2)
throw new Error("Invalid string length");
return new _Storage({ url: [fields[0], fields[1]] });
}
/**
* Converts the Storage instance to a string.
* @returns The string representation of the storage URL.
*/
toString() {
if (this.isEmpty().toBoolean()) {
return "";
}
return import_o1js3.Encoding.stringFromFields([this.url[0], this.url[1]]);
}
static empty() {
return new _Storage({ url: [(0, import_o1js3.Field)(0), (0, import_o1js3.Field)(0)] });
}
isEmpty() {
return this.url[0].equals((0, import_o1js3.Field)(0)).and(this.url[1].equals((0, import_o1js3.Field)(0)));
}
};
// dist/node/whitelist/whitelist.js
var import_o1js5 = require("o1js");
// dist/node/whitelist/offchain-map.js
var import_o1js4 = require("o1js");
var { IndexedMerkleMap: IndexedMerkleMap2 } = import_o1js4.Experimental;
var OFFCHAIN_MAP_HEIGHT = 20;
var OffchainMap = class extends IndexedMerkleMap2(OFFCHAIN_MAP_HEIGHT) {
};
var OffchainMapOption = class extends (0, import_o1js4.Option)(OffchainMap) {
};
var FieldOption = class extends (0, import_o1js4.Option)(import_o1js4.Field) {
};
var OffChainListBase = class _OffChainListBase extends (0, import_o1js4.Struct)({
/** The root hash of the Merkle tree representing the whitelist. */
root: import_o1js4.Field
}) {
isNone() {
return this.root.equals((0, import_o1js4.Field)(0));
}
isSome() {
return this.isNone().not();
}
async load(storage, name = "offchain-map") {
const isNone = this.isNone();
const map = await import_o1js4.Provable.witnessAsync(OffchainMapOption, async () => {
if (isNone.toBoolean())
return OffchainMapOption.none();
else
return OffchainMapOption.fromValue(await loadIndexedMerkleMap({
url: createIpfsURL({ hash: storage.toString() }),
type: OffchainMap,
name
}));
});
isNone.assertEquals(map.isSome.not());
const root = import_o1js4.Provable.if(map.isSome, map.orElse(new OffchainMap()).root, (0, import_o1js4.Field)(0));
root.assertEquals(this.root);
return map;
}
/**
* The function fetches a whitelisted amount associated with a given key using a map and returns it
* as a FieldOption.
* @param {Field} key - The `key` parameter is of type `Field`,
* which represents a field element in the context of a cryptographic system.
* @returns The `getValue` function returns a `Promise` that resolves to a `FieldOption`
* object. This object contains a `value` property representing the amount retrieved from a map based
* on the provided key. The `isSome` property indicates whether the value is present or not.
* The value is not present if the list is NOT empty and the key is NOT in the map.
* The value is present if the list IS empty or the key IS in the map.
* The value is present and equals to Field(0) if the list IS empty.
*/
async getValue(key, storage, name = "offchain-map") {
const map = await this.load(storage, name);
const value = map.orElse(new OffchainMap()).getOption(key);
const valueField = value.orElse((0, import_o1js4.Field)(0));
return new FieldOption({
value: valueField,
isSome: value.isSome.or(this.isNone())
});
}
static empty() {
return new _OffChainListBase({
root: (0, import_o1js4.Field)(0)
});
}
/**
* Creates a new OffchainListBase
* @param params - The parameters for creating the list.
* @param params.list - The list of entries to be added to the map.
* @param params.data - The JSON data that should be added to the IPFS storage that represent the initial data
* @returns A new `OffChainList` instance.
*/
static async create(params) {
function toField(value) {
if (!value)
return (0, import_o1js4.Field)(0);
if (typeof value === "string")
return import_o1js4.Field.fromJSON(value);
if (typeof value === "bigint" || typeof value === "number")
return (0, import_o1js4.Field)(value);
return value;
}
const list = params.list.map((item) => ({
key: toField(item.key),
value: toField(item.value)
}));
const map = new OffchainMap();
for (const item of list) {
map.insert(item.key, item.value);
}
const serializedMap = serializeIndexedMap(map);
const json = {
[params.name ?? "offchain-map"]: {
map: serializedMap,
list: list.map((item) => ({
key: item.key.toJSON(),
value: item.value?.toJSON()
})),
data: params.data
}
};
return {
listBase: new _OffChainListBase({
root: map.root
}),
json
};
}
toString() {
return JSON.stringify({ root: this.root.toJSON() }, null, 2);
}
static fromString(str) {
const json = JSON.parse(str);
return new _OffChainListBase({
root: import_o1js4.Field.fromJSON(json.root)
});
}
};
var OffChainList = class _OffChainList extends (0, import_o1js4.Struct)({
/** The root hash of the Merkle tree representing the whitelist. */
root: import_o1js4.Field,
/** Off-chain storage information, typically an IPFS hash pointing to the whitelist data. */
storage: Storage
}) {
isNone() {
return this.root.equals((0, import_o1js4.Field)(0)).or(this.storage.isEmpty());
}
isSome() {
return this.isNone().not();
}
async load(name = "offchain-map") {
const isNone = this.isNone();
const map = await import_o1js4.Provable.witnessAsync(OffchainMapOption, async () => {
if (isNone.toBoolean())
return OffchainMapOption.none();
else
return OffchainMapOption.fromValue(await loadIndexedMerkleMap({
url: createIpfsURL({ hash: this.storage.toString() }),
type: OffchainMap,
name
}));
});
isNone.assertEquals(map.isSome.not());
const root = import_o1js4.Provable.if(map.isSome, map.orElse(new OffchainMap()).root, (0, import_o1js4.Field)(0));
root.assertEquals(this.root);
return map;
}
/**
* The function fetches a whitelisted amount associated with a given key using a map and returns it
* as a FieldOption.
* @param {Field} key - The `key` parameter is of type `Field`,
* which represents a field element in the context of a cryptographic system.
* @returns The `getValue` function returns a `Promise` that resolves to a `FieldOption`
* object. This object contains a `value` property representing the amount retrieved from a map based
* on the provided key. The `isSome` property indicates whether the value is present or not.
* The value is not present if the list is NOT empty and the key is NOT in the map.
* The value is present if the list IS empty or the key IS in the map.
* The value is present and equals to Field(0) if the list IS empty.
*/
async getValue(key, name = "offchain-map") {
const map = await this.load(name);
const value = map.orElse(new OffchainMap()).getOption(key);
const valueField = value.orElse((0, import_o1js4.Field)(0));
return new FieldOption({
value: valueField,
isSome: value.isSome.or(this.isNone())
});
}
static empty() {
return new _OffChainList({
root: (0, import_o1js4.Field)(0),
storage: Storage.empty()
});
}
/**
* Creates a new OffchainList
* and pins it to IPFS.
* @param params - The parameters for creating the list.
* @param params.list - The list of entries to be added to the map.
* @param params.data - The JSON data that should be added to the IPFS storage that represent the initial data
* @returns A new `OffChainList` instance.
*/
static async create(params) {
const { name = "offchain-map", filename = "offchain-list.json", keyvalues, timeout = 60 * 1e3, attempts = 5, auth, pin = true, json: initialJson = {} } = params;
const { listBase, json: newJson } = await OffChainListBase.create({
list: params.list,
data: params.data,
name
});
const json = { ...initialJson, ...newJson };
if (process.env.DEBUG === "true")
console.log("OffChainList.create:", { json, name, keyvalues });
if (pin) {
let attempt = 0;
const start = Date.now();
let hash = await pinJSON({
data: json,
name: filename,
keyvalues,
auth
});
while (!hash && attempt < attempts && Date.now() - start < timeout) {
attempt++;
await sleep(5e3 * attempt);
hash = await pinJSON({
data: json,
name,
keyvalues,
auth
});
}
if (!hash)
throw new Error("Failed to pin OffchainMap");
return {
list: new _OffChainList({
root: listBase.root,
storage: Storage.fromString(hash)
}),
json
};
}
return {
list: new _OffChainList({
root: listBase.root,
storage: Storage.empty()
}),
json
};
}
toString() {
return JSON.stringify({ root: this.root.toJSON(), storage: this.storage.toString() }, null, 2);
}
static fromString(str) {
const json = JSON.parse(str);
return new _OffChainList({
root: import_o1js4.Field.fromJSON(json.root),
storage: Storage.fromString(json.storage)
});
}
};
// dist/node/whitelist/whitelist.js
var UInt64Option = class extends (0, import_o1js5.Option)(import_o1js5.UInt64) {
};
var WhitelistedAddress = class {
};
var Whitelist = class _Whitelist extends (0, import_o1js5.Struct)({
list: OffChainList
}) {
isNone() {
return this.list.isNone();
}
isSome() {
return this.list.isSome();
}
async load() {
return this.list.load();
}
/**
* The function fetches a whitelisted amount associated with a given address using a map and returns it
* as a UInt64Option.
* @param {PublicKey} address - The `address` parameter is of type `PublicKey`,
* used to retrieve a whitelisted amount for the given address.
* @returns The `fetchWhitelistedAmount` function returns a `Promise` that resolves to a `UInt64Option`
* object. This object contains a `value` property representing the amount retrieved from a map based
* on the provided address. The `isSome` property indicates whether the value is present or not.
* The value is not present if the whitelist is NOT empty and the address is NOT whitelisted.
* The value is present if the whitelist is NOT empty or the address IS whitelisted.
* The value is present and equals to UInt64.MAXINT() if the whitelist IS empty.
*/
async getWhitelistedAmount(address, name = "whitelist") {
const map = await this.list.load(name);
const key = import_o1js5.Poseidon.hashPacked(import_o1js5.PublicKey, address);
const value = map.orElse(new OffchainMap()).getOption(key);
const valueField = value.orElse(import_o1js5.UInt64.MAXINT().value);
valueField.assertLessThanOrEqual(import_o1js5.UInt64.MAXINT().value);
const amount = import_o1js5.UInt64.Unsafe.fromField(valueField);
return new UInt64Option({
value: amount,
isSome: value.isSome.or(this.isNone())
});
}
static empty() {
return new _Whitelist({
list: OffChainList.empty()
});
}
/**
* Creates a new whitelist and pins it to IPFS.
* @param params - The parameters for creating the whitelist.
* @returns A new `Whitelist` instance.
*/
static async create(params) {
const { name = "whitelist", filename = "whitelist.json", keyvalues, timeout, attempts, auth, pin = true, json: initialJson = {} } = params;
function parseAddress(address) {
return typeof address === "string" ? import_o1js5.PublicKey.fromBase58(address) : address;
}
function parseAmount(amount) {
if (amount === void 0)
return import_o1js5.UInt64.zero;
return typeof amount === "number" ? import_o1js5.UInt64.from(Math.round(amount)) : amount;
}
const entries = params.list.map((item) => ({
address: parseAddress(item.address),
amount: parseAmount(item.amount)
}));
const { list, json } = await OffChainList.create({
list: entries.map((item) => ({
key: import_o1js5.Poseidon.hashPacked(import_o1js5.PublicKey, item.address),
value: item.amount.value
})),
data: entries.map((item) => ({
address: item.address.toBase58(),
amount: Number(item.amount.toBigInt())
})),
name,
filename,
keyvalues,
timeout,
attempts,
auth,
pin,
json: initialJson
});
return { whitelist: new _Whitelist({ list }), json };
}
toString() {
return this.list.toString();
}
static fromString(str) {
return new _Whitelist({ list: OffChainList.fromString(str) });
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FieldOption,
OffChainList,
OffChainListBase,
OffchainMap,
OffchainMapOption,
Storage,
UInt64Option,
Whitelist,
WhitelistedAddress,
bigintFromBase56,
bigintFromBase64,
bigintToBase56,
bigintToBase64,
createIpfsURL,
deserializeIndexedMerkleMap,
fieldFromBase56,
fieldFromBase64,
fieldToBase56,
fieldToBase64,
fromBase,
loadIndexedMerkleMap,
parseIndexedMapSerialized,
pinJSON,
saveIndexedMerkleMap,
serializeIndexedMap,
sleep,
toBase
});