@silvana-one/storage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/node/base64/bigint.d.ts +6 -0
- package/dist/node/base64/bigint.js +83 -0
- package/dist/node/base64/bigint.js.map +1 -0
- package/dist/node/base64/field.d.ts +5 -0
- package/dist/node/base64/field.js +29 -0
- package/dist/node/base64/field.js.map +1 -0
- package/dist/node/base64/index.d.ts +2 -0
- package/dist/node/base64/index.js +3 -0
- package/dist/node/base64/index.js.map +1 -0
- package/dist/node/index.cjs +735 -0
- package/dist/node/index.d.ts +5 -0
- package/dist/node/index.js +6 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/indexed-map/index.d.ts +1 -0
- package/dist/node/indexed-map/index.js +2 -0
- package/dist/node/indexed-map/index.js.map +1 -0
- package/dist/node/indexed-map/indexed-map.d.ts +39 -0
- package/dist/node/indexed-map/indexed-map.js +125 -0
- package/dist/node/indexed-map/indexed-map.js.map +1 -0
- package/dist/node/storage/index.d.ts +3 -0
- package/dist/node/storage/index.js +4 -0
- package/dist/node/storage/index.js.map +1 -0
- package/dist/node/storage/ipfs.d.ts +5 -0
- package/dist/node/storage/ipfs.js +16 -0
- package/dist/node/storage/ipfs.js.map +1 -0
- package/dist/node/storage/pinata.d.ts +6 -0
- package/dist/node/storage/pinata.js +40 -0
- package/dist/node/storage/pinata.js.map +1 -0
- package/dist/node/storage/storage.d.ts +78 -0
- package/dist/node/storage/storage.js +60 -0
- package/dist/node/storage/storage.js.map +1 -0
- package/dist/node/util/index.d.ts +1 -0
- package/dist/node/util/index.js +2 -0
- package/dist/node/util/index.js.map +1 -0
- package/dist/node/util/sleep.d.ts +1 -0
- package/dist/node/util/sleep.js +4 -0
- package/dist/node/util/sleep.js.map +1 -0
- package/dist/node/whitelist/index.d.ts +2 -0
- package/dist/node/whitelist/index.js +3 -0
- package/dist/node/whitelist/index.js.map +1 -0
- package/dist/node/whitelist/offchain-map.d.ts +352 -0
- package/dist/node/whitelist/offchain-map.js +247 -0
- package/dist/node/whitelist/offchain-map.js.map +1 -0
- package/dist/node/whitelist/whitelist.d.ts +122 -0
- package/dist/node/whitelist/whitelist.js +98 -0
- package/dist/node/whitelist/whitelist.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsconfig.web.tsbuildinfo +1 -0
- package/dist/web/base64/bigint.d.ts +6 -0
- package/dist/web/base64/bigint.js +83 -0
- package/dist/web/base64/bigint.js.map +1 -0
- package/dist/web/base64/field.d.ts +5 -0
- package/dist/web/base64/field.js +29 -0
- package/dist/web/base64/field.js.map +1 -0
- package/dist/web/base64/index.d.ts +2 -0
- package/dist/web/base64/index.js +3 -0
- package/dist/web/base64/index.js.map +1 -0
- package/dist/web/index.d.ts +5 -0
- package/dist/web/index.js +6 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/indexed-map/index.d.ts +1 -0
- package/dist/web/indexed-map/index.js +2 -0
- package/dist/web/indexed-map/index.js.map +1 -0
- package/dist/web/indexed-map/indexed-map.d.ts +39 -0
- package/dist/web/indexed-map/indexed-map.js +125 -0
- package/dist/web/indexed-map/indexed-map.js.map +1 -0
- package/dist/web/storage/index.d.ts +3 -0
- package/dist/web/storage/index.js +4 -0
- package/dist/web/storage/index.js.map +1 -0
- package/dist/web/storage/ipfs.d.ts +5 -0
- package/dist/web/storage/ipfs.js +16 -0
- package/dist/web/storage/ipfs.js.map +1 -0
- package/dist/web/storage/pinata.d.ts +6 -0
- package/dist/web/storage/pinata.js +40 -0
- package/dist/web/storage/pinata.js.map +1 -0
- package/dist/web/storage/storage.d.ts +78 -0
- package/dist/web/storage/storage.js +60 -0
- package/dist/web/storage/storage.js.map +1 -0
- package/dist/web/util/index.d.ts +1 -0
- package/dist/web/util/index.js +2 -0
- package/dist/web/util/index.js.map +1 -0
- package/dist/web/util/sleep.d.ts +1 -0
- package/dist/web/util/sleep.js +4 -0
- package/dist/web/util/sleep.js.map +1 -0
- package/dist/web/whitelist/index.d.ts +2 -0
- package/dist/web/whitelist/index.js +3 -0
- package/dist/web/whitelist/index.js.map +1 -0
- package/dist/web/whitelist/offchain-map.d.ts +352 -0
- package/dist/web/whitelist/offchain-map.js +247 -0
- package/dist/web/whitelist/offchain-map.js.map +1 -0
- package/dist/web/whitelist/whitelist.d.ts +122 -0
- package/dist/web/whitelist/whitelist.js +98 -0
- package/dist/web/whitelist/whitelist.js.map +1 -0
- package/package.json +60 -0
- package/src/base64/bigint.ts +87 -0
- package/src/base64/field.ts +34 -0
- package/src/base64/index.ts +2 -0
- package/src/index.ts +5 -0
- package/src/indexed-map/index.ts +1 -0
- package/src/indexed-map/indexed-map.ts +189 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/ipfs.ts +20 -0
- package/src/storage/pinata.ts +56 -0
- package/src/storage/storage.ts +65 -0
- package/src/util/index.ts +1 -0
- package/src/util/sleep.ts +3 -0
- package/src/whitelist/index.ts +2 -0
- package/src/whitelist/offchain-map.ts +341 -0
- package/src/whitelist/whitelist.ts +139 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export async function pinJSON(params: {
|
|
2
|
+
data: string | object;
|
|
3
|
+
name?: string;
|
|
4
|
+
keyvalues?: object;
|
|
5
|
+
auth?: string;
|
|
6
|
+
}): Promise<string | undefined> {
|
|
7
|
+
const {
|
|
8
|
+
data,
|
|
9
|
+
name = "data.json",
|
|
10
|
+
keyvalues = { library: "zkcloudworker" },
|
|
11
|
+
} = params;
|
|
12
|
+
const auth =
|
|
13
|
+
params.auth ??
|
|
14
|
+
process.env.PINATA_JWT ??
|
|
15
|
+
process.env.NEXT_PUBLIC_PINATA_JWT ??
|
|
16
|
+
process.env.REACT_APP_PINATA_JWT;
|
|
17
|
+
if (!auth)
|
|
18
|
+
throw new Error(
|
|
19
|
+
"pinJSON: auth, PINATA_JWT, NEXT_PUBLIC_PINATA_JWT or REACT_APP_PINATA_JWT should be defined"
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const pinataData = {
|
|
24
|
+
pinataOptions: {
|
|
25
|
+
cidVersion: 1,
|
|
26
|
+
},
|
|
27
|
+
pinataMetadata: {
|
|
28
|
+
name,
|
|
29
|
+
keyvalues,
|
|
30
|
+
},
|
|
31
|
+
pinataContent: data,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const res = await fetch("https://api.pinata.cloud/pinning/pinJSONToIPFS", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
Authorization: "Bearer " + auth,
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify(pinataData),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
throw new Error(`Pinata error: status: ${res.status} ${res.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const responseData = await res.json();
|
|
48
|
+
console.log("saveToIPFS result:", responseData);
|
|
49
|
+
return (
|
|
50
|
+
responseData as unknown as { IpfsHash: string | undefined } | undefined
|
|
51
|
+
)?.IpfsHash;
|
|
52
|
+
} catch (error: any) {
|
|
53
|
+
console.error("saveToIPFS error:", error?.message);
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Field, Bool, Struct, Encoding, Provable } from "o1js";
|
|
2
|
+
|
|
3
|
+
export type IpfsHash = string;
|
|
4
|
+
/**
|
|
5
|
+
* Represents the off-chain storage information,
|
|
6
|
+
* such as an IPFS hash.
|
|
7
|
+
*/
|
|
8
|
+
export class Storage extends Struct({
|
|
9
|
+
url: Provable.Array(Field, 2),
|
|
10
|
+
}) {
|
|
11
|
+
constructor(value: { url: [Field, Field] }) {
|
|
12
|
+
super(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Asserts that two Storage instances are equal.
|
|
17
|
+
* @param a The first Storage instance.
|
|
18
|
+
* @param b The second Storage instance.
|
|
19
|
+
*/
|
|
20
|
+
static assertEquals(a: Storage, b: Storage) {
|
|
21
|
+
a.url[0].assertEquals(b.url[0]);
|
|
22
|
+
a.url[1].assertEquals(b.url[1]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Checks if two Storage instances are equal.
|
|
27
|
+
* @param a The first Storage instance.
|
|
28
|
+
* @param b The second Storage instance.
|
|
29
|
+
* @returns A Bool indicating whether the two instances are equal.
|
|
30
|
+
*/
|
|
31
|
+
static equals(a: Storage, b: Storage): Bool {
|
|
32
|
+
return a.url[0].equals(b.url[0]).and(a.url[1].equals(b.url[1]));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a Storage instance from a string.
|
|
37
|
+
* @param url The string representing the storage URL.
|
|
38
|
+
* @returns A new Storage instance.
|
|
39
|
+
*/
|
|
40
|
+
static fromString(url: IpfsHash): Storage {
|
|
41
|
+
if (url === "") return Storage.empty();
|
|
42
|
+
const fields = Encoding.stringToFields(url);
|
|
43
|
+
if (fields.length !== 2) throw new Error("Invalid string length");
|
|
44
|
+
return new Storage({ url: [fields[0], fields[1]] });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Converts the Storage instance to a string.
|
|
49
|
+
* @returns The string representation of the storage URL.
|
|
50
|
+
*/
|
|
51
|
+
toString(): IpfsHash {
|
|
52
|
+
if (this.isEmpty().toBoolean()) {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
return Encoding.stringFromFields([this.url[0], this.url[1]]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static empty(): Storage {
|
|
59
|
+
return new Storage({ url: [Field(0), Field(0)] });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
isEmpty(): Bool {
|
|
63
|
+
return this.url[0].equals(Field(0)).and(this.url[1].equals(Field(0)));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./sleep.js";
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { Experimental, Struct, Field, Option, Provable, Bool } from "o1js";
|
|
2
|
+
import {
|
|
3
|
+
serializeIndexedMap,
|
|
4
|
+
loadIndexedMerkleMap,
|
|
5
|
+
IndexedMapSerialized,
|
|
6
|
+
IndexedMapSerializedJson,
|
|
7
|
+
} from "../indexed-map/indexed-map.js";
|
|
8
|
+
import { sleep } from "../util/sleep.js";
|
|
9
|
+
import { Storage, IpfsHash } from "../storage/storage.js";
|
|
10
|
+
import { createIpfsURL } from "../storage/ipfs.js";
|
|
11
|
+
import { pinJSON } from "../storage/pinata.js";
|
|
12
|
+
|
|
13
|
+
const { IndexedMerkleMap } = Experimental;
|
|
14
|
+
type IndexedMerkleMap = Experimental.IndexedMerkleMap;
|
|
15
|
+
const OFFCHAIN_MAP_HEIGHT = 20;
|
|
16
|
+
|
|
17
|
+
export interface OffchainMapSerialized extends IndexedMapSerializedJson {
|
|
18
|
+
[key: string]: {
|
|
19
|
+
map: IndexedMapSerialized;
|
|
20
|
+
list: { key: string; value?: string }[];
|
|
21
|
+
data?: object;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Represents the Offchain Map using an Indexed Merkle Map. */
|
|
26
|
+
export class OffchainMap extends IndexedMerkleMap(OFFCHAIN_MAP_HEIGHT) {}
|
|
27
|
+
export class OffchainMapOption extends Option(OffchainMap) {}
|
|
28
|
+
export class FieldOption extends Option(Field) {}
|
|
29
|
+
export interface OffChainMapEntry {
|
|
30
|
+
key: Field | bigint | number | string;
|
|
31
|
+
value?: Field | bigint | number | string; // default is 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* IpfsHash is used when the map is already pinned to IPFS, it should be string with IPFS hash
|
|
36
|
+
*/
|
|
37
|
+
export type OffChainMapEntries = OffChainMapEntry[] | IpfsHash;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Base class for offchain lists and maps that does not have storage
|
|
41
|
+
*/
|
|
42
|
+
export class OffChainListBase extends Struct({
|
|
43
|
+
/** The root hash of the Merkle tree representing the whitelist. */
|
|
44
|
+
root: Field,
|
|
45
|
+
}) {
|
|
46
|
+
isNone(): Bool {
|
|
47
|
+
return this.root.equals(Field(0));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isSome(): Bool {
|
|
51
|
+
return this.isNone().not();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async load(
|
|
55
|
+
storage: Storage,
|
|
56
|
+
name: string = "offchain-map"
|
|
57
|
+
): Promise<OffchainMapOption> {
|
|
58
|
+
const isNone = this.isNone();
|
|
59
|
+
const map = await Provable.witnessAsync(OffchainMapOption, async () => {
|
|
60
|
+
if (isNone.toBoolean()) return OffchainMapOption.none();
|
|
61
|
+
else
|
|
62
|
+
return OffchainMapOption.fromValue(
|
|
63
|
+
await loadIndexedMerkleMap({
|
|
64
|
+
url: createIpfsURL({ hash: storage.toString() }),
|
|
65
|
+
type: OffchainMap,
|
|
66
|
+
name,
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
isNone.assertEquals(map.isSome.not());
|
|
71
|
+
const root = Provable.if(
|
|
72
|
+
map.isSome,
|
|
73
|
+
map.orElse(new OffchainMap()).root,
|
|
74
|
+
Field(0)
|
|
75
|
+
);
|
|
76
|
+
root.assertEquals(this.root);
|
|
77
|
+
return map;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The function fetches a whitelisted amount associated with a given key using a map and returns it
|
|
82
|
+
* as a FieldOption.
|
|
83
|
+
* @param {Field} key - The `key` parameter is of type `Field`,
|
|
84
|
+
* which represents a field element in the context of a cryptographic system.
|
|
85
|
+
* @returns The `getValue` function returns a `Promise` that resolves to a `FieldOption`
|
|
86
|
+
* object. This object contains a `value` property representing the amount retrieved from a map based
|
|
87
|
+
* on the provided key. The `isSome` property indicates whether the value is present or not.
|
|
88
|
+
* The value is not present if the list is NOT empty and the key is NOT in the map.
|
|
89
|
+
* The value is present if the list IS empty or the key IS in the map.
|
|
90
|
+
* The value is present and equals to Field(0) if the list IS empty.
|
|
91
|
+
*/
|
|
92
|
+
async getValue(
|
|
93
|
+
key: Field,
|
|
94
|
+
storage: Storage,
|
|
95
|
+
name: string = "offchain-map"
|
|
96
|
+
): Promise<FieldOption> {
|
|
97
|
+
const map = await this.load(storage, name);
|
|
98
|
+
const value = map.orElse(new OffchainMap()).getOption(key);
|
|
99
|
+
const valueField = value.orElse(Field(0));
|
|
100
|
+
return new FieldOption({
|
|
101
|
+
value: valueField,
|
|
102
|
+
isSome: value.isSome.or(this.isNone()),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static empty(): OffChainListBase {
|
|
107
|
+
return new OffChainListBase({
|
|
108
|
+
root: Field(0),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Creates a new OffchainListBase
|
|
114
|
+
* @param params - The parameters for creating the list.
|
|
115
|
+
* @param params.list - The list of entries to be added to the map.
|
|
116
|
+
* @param params.data - The JSON data that should be added to the IPFS storage that represent the initial data
|
|
117
|
+
* @returns A new `OffChainList` instance.
|
|
118
|
+
*/
|
|
119
|
+
static async create(params: {
|
|
120
|
+
list: OffChainMapEntry[] | { key: string; value?: number }[];
|
|
121
|
+
name?: string;
|
|
122
|
+
data?: object;
|
|
123
|
+
}): Promise<{
|
|
124
|
+
listBase: OffChainListBase;
|
|
125
|
+
json: OffchainMapSerialized;
|
|
126
|
+
}> {
|
|
127
|
+
function toField(
|
|
128
|
+
value: bigint | string | number | Field | undefined
|
|
129
|
+
): Field {
|
|
130
|
+
if (!value) return Field(0);
|
|
131
|
+
if (typeof value === "string") return Field.fromJSON(value);
|
|
132
|
+
if (typeof value === "bigint" || typeof value === "number")
|
|
133
|
+
return Field(value);
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const list = params.list.map((item) => ({
|
|
138
|
+
key: toField(item.key),
|
|
139
|
+
value: toField(item.value),
|
|
140
|
+
}));
|
|
141
|
+
const map = new OffchainMap();
|
|
142
|
+
for (const item of list) {
|
|
143
|
+
map.insert(item.key, item.value);
|
|
144
|
+
}
|
|
145
|
+
const serializedMap = serializeIndexedMap(map);
|
|
146
|
+
const json: OffchainMapSerialized = {
|
|
147
|
+
[params.name ?? "offchain-map"]: {
|
|
148
|
+
map: serializedMap,
|
|
149
|
+
list: list.map((item) => ({
|
|
150
|
+
key: item.key.toJSON(),
|
|
151
|
+
value: item.value?.toJSON(),
|
|
152
|
+
})),
|
|
153
|
+
data: params.data,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
listBase: new OffChainListBase({
|
|
159
|
+
root: map.root,
|
|
160
|
+
}),
|
|
161
|
+
json,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
toString(): string {
|
|
166
|
+
return JSON.stringify({ root: this.root.toJSON() }, null, 2);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static fromString(str: string): OffChainListBase {
|
|
170
|
+
const json = JSON.parse(str);
|
|
171
|
+
return new OffChainListBase({
|
|
172
|
+
root: Field.fromJSON(json.root),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export class OffChainList extends Struct({
|
|
178
|
+
/** The root hash of the Merkle tree representing the whitelist. */
|
|
179
|
+
root: Field,
|
|
180
|
+
/** Off-chain storage information, typically an IPFS hash pointing to the whitelist data. */
|
|
181
|
+
storage: Storage,
|
|
182
|
+
}) {
|
|
183
|
+
isNone(): Bool {
|
|
184
|
+
return this.root.equals(Field(0)).or(this.storage.isEmpty());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
isSome(): Bool {
|
|
188
|
+
return this.isNone().not();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async load(
|
|
192
|
+
name: string | undefined = "offchain-map"
|
|
193
|
+
): Promise<OffchainMapOption> {
|
|
194
|
+
const isNone = this.isNone();
|
|
195
|
+
const map = await Provable.witnessAsync(OffchainMapOption, async () => {
|
|
196
|
+
if (isNone.toBoolean()) return OffchainMapOption.none();
|
|
197
|
+
else
|
|
198
|
+
return OffchainMapOption.fromValue(
|
|
199
|
+
await loadIndexedMerkleMap({
|
|
200
|
+
url: createIpfsURL({ hash: this.storage.toString() }),
|
|
201
|
+
type: OffchainMap,
|
|
202
|
+
name,
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
isNone.assertEquals(map.isSome.not());
|
|
207
|
+
const root = Provable.if(
|
|
208
|
+
map.isSome,
|
|
209
|
+
map.orElse(new OffchainMap()).root,
|
|
210
|
+
Field(0)
|
|
211
|
+
);
|
|
212
|
+
root.assertEquals(this.root);
|
|
213
|
+
return map;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The function fetches a whitelisted amount associated with a given key using a map and returns it
|
|
218
|
+
* as a FieldOption.
|
|
219
|
+
* @param {Field} key - The `key` parameter is of type `Field`,
|
|
220
|
+
* which represents a field element in the context of a cryptographic system.
|
|
221
|
+
* @returns The `getValue` function returns a `Promise` that resolves to a `FieldOption`
|
|
222
|
+
* object. This object contains a `value` property representing the amount retrieved from a map based
|
|
223
|
+
* on the provided key. The `isSome` property indicates whether the value is present or not.
|
|
224
|
+
* The value is not present if the list is NOT empty and the key is NOT in the map.
|
|
225
|
+
* The value is present if the list IS empty or the key IS in the map.
|
|
226
|
+
* The value is present and equals to Field(0) if the list IS empty.
|
|
227
|
+
*/
|
|
228
|
+
async getValue(
|
|
229
|
+
key: Field,
|
|
230
|
+
name: string | undefined = "offchain-map"
|
|
231
|
+
): Promise<FieldOption> {
|
|
232
|
+
const map = await this.load(name);
|
|
233
|
+
const value = map.orElse(new OffchainMap()).getOption(key);
|
|
234
|
+
const valueField = value.orElse(Field(0));
|
|
235
|
+
return new FieldOption({
|
|
236
|
+
value: valueField,
|
|
237
|
+
isSome: value.isSome.or(this.isNone()),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
static empty(): OffChainList {
|
|
242
|
+
return new OffChainList({
|
|
243
|
+
root: Field(0),
|
|
244
|
+
storage: Storage.empty(),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Creates a new OffchainList
|
|
250
|
+
* and pins it to IPFS.
|
|
251
|
+
* @param params - The parameters for creating the list.
|
|
252
|
+
* @param params.list - The list of entries to be added to the map.
|
|
253
|
+
* @param params.data - The JSON data that should be added to the IPFS storage that represent the initial data
|
|
254
|
+
* @returns A new `OffChainList` instance.
|
|
255
|
+
*/
|
|
256
|
+
static async create(params: {
|
|
257
|
+
list: OffChainMapEntry[] | { key: string; value?: number }[];
|
|
258
|
+
data?: object;
|
|
259
|
+
name?: string;
|
|
260
|
+
filename?: string;
|
|
261
|
+
keyvalues?: object;
|
|
262
|
+
timeout?: number;
|
|
263
|
+
attempts?: number;
|
|
264
|
+
auth?: string;
|
|
265
|
+
pin?: boolean;
|
|
266
|
+
json?: OffchainMapSerialized;
|
|
267
|
+
}): Promise<{ list: OffChainList; json: OffchainMapSerialized }> {
|
|
268
|
+
const {
|
|
269
|
+
name = "offchain-map",
|
|
270
|
+
filename = "offchain-list.json",
|
|
271
|
+
keyvalues,
|
|
272
|
+
timeout = 60 * 1000,
|
|
273
|
+
attempts = 5,
|
|
274
|
+
auth,
|
|
275
|
+
pin = true,
|
|
276
|
+
json: initialJson = {},
|
|
277
|
+
} = params;
|
|
278
|
+
|
|
279
|
+
const { listBase, json: newJson } = await OffChainListBase.create({
|
|
280
|
+
list: params.list,
|
|
281
|
+
data: params.data,
|
|
282
|
+
name,
|
|
283
|
+
});
|
|
284
|
+
const json = { ...initialJson, ...newJson };
|
|
285
|
+
|
|
286
|
+
if (process.env.DEBUG === "true")
|
|
287
|
+
console.log("OffChainList.create:", { json, name, keyvalues });
|
|
288
|
+
if (pin) {
|
|
289
|
+
let attempt = 0;
|
|
290
|
+
const start = Date.now();
|
|
291
|
+
let hash = await pinJSON({
|
|
292
|
+
data: json,
|
|
293
|
+
name: filename,
|
|
294
|
+
keyvalues,
|
|
295
|
+
auth,
|
|
296
|
+
});
|
|
297
|
+
while (!hash && attempt < attempts && Date.now() - start < timeout) {
|
|
298
|
+
attempt++;
|
|
299
|
+
await sleep(5000 * attempt); // handle rate-limits
|
|
300
|
+
hash = await pinJSON({
|
|
301
|
+
data: json,
|
|
302
|
+
name,
|
|
303
|
+
keyvalues,
|
|
304
|
+
auth,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
if (!hash) throw new Error("Failed to pin OffchainMap");
|
|
308
|
+
return {
|
|
309
|
+
list: new OffChainList({
|
|
310
|
+
root: listBase.root,
|
|
311
|
+
storage: Storage.fromString(hash),
|
|
312
|
+
}),
|
|
313
|
+
json,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
list: new OffChainList({
|
|
319
|
+
root: listBase.root,
|
|
320
|
+
storage: Storage.empty(),
|
|
321
|
+
}),
|
|
322
|
+
json,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
toString(): string {
|
|
327
|
+
return JSON.stringify(
|
|
328
|
+
{ root: this.root.toJSON(), storage: this.storage.toString() },
|
|
329
|
+
null,
|
|
330
|
+
2
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
static fromString(str: string): OffChainList {
|
|
335
|
+
const json = JSON.parse(str);
|
|
336
|
+
return new OffChainList({
|
|
337
|
+
root: Field.fromJSON(json.root),
|
|
338
|
+
storage: Storage.fromString(json.storage),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Struct, Field, Option, PublicKey, UInt64, Poseidon, Bool } from "o1js";
|
|
2
|
+
import {
|
|
3
|
+
OffChainList,
|
|
4
|
+
OffchainMapOption,
|
|
5
|
+
OffchainMap,
|
|
6
|
+
OffchainMapSerialized,
|
|
7
|
+
} from "./offchain-map.js";
|
|
8
|
+
|
|
9
|
+
export class UInt64Option extends Option(UInt64) {}
|
|
10
|
+
export class WhitelistedAddress {
|
|
11
|
+
address: PublicKey | string;
|
|
12
|
+
amount?: UInt64 | number; // Maximum permitted amount of the whitelisted transaction, default is 0
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class Whitelist extends Struct({
|
|
16
|
+
list: OffChainList,
|
|
17
|
+
}) {
|
|
18
|
+
isNone(): Bool {
|
|
19
|
+
return this.list.isNone();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
isSome(): Bool {
|
|
23
|
+
return this.list.isSome();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async load(): Promise<OffchainMapOption> {
|
|
27
|
+
return this.list.load();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The function fetches a whitelisted amount associated with a given address using a map and returns it
|
|
32
|
+
* as a UInt64Option.
|
|
33
|
+
* @param {PublicKey} address - The `address` parameter is of type `PublicKey`,
|
|
34
|
+
* used to retrieve a whitelisted amount for the given address.
|
|
35
|
+
* @returns The `fetchWhitelistedAmount` function returns a `Promise` that resolves to a `UInt64Option`
|
|
36
|
+
* object. This object contains a `value` property representing the amount retrieved from a map based
|
|
37
|
+
* on the provided address. The `isSome` property indicates whether the value is present or not.
|
|
38
|
+
* The value is not present if the whitelist is NOT empty and the address is NOT whitelisted.
|
|
39
|
+
* The value is present if the whitelist is NOT empty or the address IS whitelisted.
|
|
40
|
+
* The value is present and equals to UInt64.MAXINT() if the whitelist IS empty.
|
|
41
|
+
*/
|
|
42
|
+
async getWhitelistedAmount(
|
|
43
|
+
address: PublicKey,
|
|
44
|
+
name: string = "whitelist"
|
|
45
|
+
): Promise<UInt64Option> {
|
|
46
|
+
const map = await this.list.load(name);
|
|
47
|
+
const key = Poseidon.hashPacked(PublicKey, address);
|
|
48
|
+
const value = map.orElse(new OffchainMap()).getOption(key);
|
|
49
|
+
const valueField = value.orElse(UInt64.MAXINT().value);
|
|
50
|
+
valueField.assertLessThanOrEqual(UInt64.MAXINT().value);
|
|
51
|
+
const amount = UInt64.Unsafe.fromField(valueField);
|
|
52
|
+
return new UInt64Option({
|
|
53
|
+
value: amount,
|
|
54
|
+
isSome: value.isSome.or(this.isNone()),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static empty(): Whitelist {
|
|
59
|
+
return new Whitelist({
|
|
60
|
+
list: OffChainList.empty(),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates a new whitelist and pins it to IPFS.
|
|
66
|
+
* @param params - The parameters for creating the whitelist.
|
|
67
|
+
* @returns A new `Whitelist` instance.
|
|
68
|
+
*/
|
|
69
|
+
static async create(params: {
|
|
70
|
+
list: WhitelistedAddress[];
|
|
71
|
+
name?: string;
|
|
72
|
+
filename?: string;
|
|
73
|
+
keyvalues?: object;
|
|
74
|
+
timeout?: number;
|
|
75
|
+
attempts?: number;
|
|
76
|
+
auth?: string;
|
|
77
|
+
pin?: boolean;
|
|
78
|
+
json?: OffchainMapSerialized;
|
|
79
|
+
}): Promise<{ whitelist: Whitelist; json: OffchainMapSerialized }> {
|
|
80
|
+
const {
|
|
81
|
+
name = "whitelist",
|
|
82
|
+
filename = "whitelist.json",
|
|
83
|
+
keyvalues,
|
|
84
|
+
timeout,
|
|
85
|
+
attempts,
|
|
86
|
+
auth,
|
|
87
|
+
pin = true,
|
|
88
|
+
json: initialJson = {},
|
|
89
|
+
} = params;
|
|
90
|
+
|
|
91
|
+
function parseAddress(address: string | PublicKey): PublicKey {
|
|
92
|
+
return typeof address === "string"
|
|
93
|
+
? PublicKey.fromBase58(address)
|
|
94
|
+
: address;
|
|
95
|
+
}
|
|
96
|
+
function parseAmount(amount?: number | UInt64): UInt64 {
|
|
97
|
+
if (amount === undefined) return UInt64.zero;
|
|
98
|
+
return typeof amount === "number"
|
|
99
|
+
? UInt64.from(Math.round(amount))
|
|
100
|
+
: amount;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const entries: { address: PublicKey; amount: UInt64 }[] = params.list.map(
|
|
104
|
+
(item) => ({
|
|
105
|
+
address: parseAddress(item.address),
|
|
106
|
+
amount: parseAmount(item.amount),
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const { list, json } = await OffChainList.create({
|
|
111
|
+
list: entries.map((item) => ({
|
|
112
|
+
key: Poseidon.hashPacked(PublicKey, item.address),
|
|
113
|
+
value: item.amount.value,
|
|
114
|
+
})),
|
|
115
|
+
data: entries.map((item) => ({
|
|
116
|
+
address: item.address.toBase58(),
|
|
117
|
+
amount: Number(item.amount.toBigInt()),
|
|
118
|
+
})),
|
|
119
|
+
name,
|
|
120
|
+
filename,
|
|
121
|
+
keyvalues,
|
|
122
|
+
timeout,
|
|
123
|
+
attempts,
|
|
124
|
+
auth,
|
|
125
|
+
pin,
|
|
126
|
+
json: initialJson,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return { whitelist: new Whitelist({ list }), json };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
toString(): string {
|
|
133
|
+
return this.list.toString();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static fromString(str: string): Whitelist {
|
|
137
|
+
return new Whitelist({ list: OffChainList.fromString(str) });
|
|
138
|
+
}
|
|
139
|
+
}
|