@zga/client 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/dist/addresses.d.ts +29 -0
- package/dist/addresses.js +43 -0
- package/dist/billing.d.ts +41 -0
- package/dist/billing.js +141 -0
- package/dist/bin/crank.d.ts +2 -0
- package/dist/bin/crank.js +34 -0
- package/dist/bin/relayer.d.ts +2 -0
- package/dist/bin/relayer.js +37 -0
- package/dist/chain.d.ts +14 -0
- package/dist/chain.js +48 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +61 -0
- package/dist/crank/gate.d.ts +2 -0
- package/dist/crank/gate.js +26 -0
- package/dist/crank/lending.d.ts +16 -0
- package/dist/crank/lending.js +144 -0
- package/dist/crank/prices.d.ts +11 -0
- package/dist/crank/prices.js +177 -0
- package/dist/crank/runner.d.ts +15 -0
- package/dist/crank/runner.js +30 -0
- package/dist/crank/shieldFees.d.ts +15 -0
- package/dist/crank/shieldFees.js +161 -0
- package/dist/epoch-root.d.ts +23 -0
- package/dist/epoch-root.js +98 -0
- package/dist/explorer.d.ts +41 -0
- package/dist/explorer.js +222 -0
- package/dist/gossip.d.ts +4 -0
- package/dist/gossip.js +77 -0
- package/dist/idl.d.ts +2 -0
- package/dist/idl.js +52 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +47 -0
- package/dist/lending.d.ts +82 -0
- package/dist/lending.js +164 -0
- package/dist/log.d.ts +27 -0
- package/dist/log.js +173 -0
- package/dist/lookupTable.d.ts +2 -0
- package/dist/lookupTable.js +47 -0
- package/dist/observer.d.ts +59 -0
- package/dist/observer.js +255 -0
- package/dist/receipt.d.ts +30 -0
- package/dist/receipt.js +27 -0
- package/dist/relayer/chain.d.ts +35 -0
- package/dist/relayer/chain.js +171 -0
- package/dist/relayer/errors.d.ts +8 -0
- package/dist/relayer/errors.js +29 -0
- package/dist/relayer/limits.d.ts +15 -0
- package/dist/relayer/limits.js +47 -0
- package/dist/relayer/relayer.d.ts +25 -0
- package/dist/relayer/relayer.js +54 -0
- package/dist/relayer/server.d.ts +8 -0
- package/dist/relayer/server.js +101 -0
- package/dist/relayer/settings.d.ts +23 -0
- package/dist/relayer/settings.js +69 -0
- package/dist/replay.d.ts +13 -0
- package/dist/replay.js +118 -0
- package/dist/session.d.ts +29 -0
- package/dist/session.js +31 -0
- package/dist/settlement.d.ts +17 -0
- package/dist/settlement.js +96 -0
- package/dist/sha256.d.ts +3 -0
- package/dist/sha256.js +83 -0
- package/dist/shieldLimits.d.ts +15 -0
- package/dist/shieldLimits.js +24 -0
- package/dist/signer.d.ts +5 -0
- package/dist/signer.js +43 -0
- package/dist/token.d.ts +6 -0
- package/dist/token.js +33 -0
- package/package.json +36 -4
- package/README.md +0 -3
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.epochSlotRange = epochSlotRange;
|
|
37
|
+
exports.epochRoot = epochRoot;
|
|
38
|
+
exports.collectProgramTxs = collectProgramTxs;
|
|
39
|
+
exports.indexerRoot = indexerRoot;
|
|
40
|
+
const anchor = __importStar(require("@coral-xyz/anchor"));
|
|
41
|
+
const sha256_1 = require("./sha256");
|
|
42
|
+
const log_1 = require("./log");
|
|
43
|
+
const chain_1 = require("./chain");
|
|
44
|
+
const ROOT_TAG = new TextEncoder().encode('zga-indexer-root-v1');
|
|
45
|
+
const LEAF_TAG = Uint8Array.of(0x00);
|
|
46
|
+
const PAGE = 1000;
|
|
47
|
+
function u64le(n) {
|
|
48
|
+
const b = new Uint8Array(8);
|
|
49
|
+
new DataView(b.buffer).setBigUint64(0, n, true);
|
|
50
|
+
return b;
|
|
51
|
+
}
|
|
52
|
+
function epochSlotRange(configStartSlot, epoch, epochSlots = chain_1.EPOCH_DURATION_SLOTS) {
|
|
53
|
+
const start = configStartSlot + epoch * epochSlots;
|
|
54
|
+
return { start, end: start + epochSlots };
|
|
55
|
+
}
|
|
56
|
+
function epochRoot(committedEpoch, txs) {
|
|
57
|
+
const unique = new Map();
|
|
58
|
+
for (const t of txs) {
|
|
59
|
+
if (!Number.isInteger(t.slot) || t.slot < 0)
|
|
60
|
+
throw new Error('A transaction carries an invalid slot');
|
|
61
|
+
const sig = anchor.utils.bytes.bs58.decode(t.signature);
|
|
62
|
+
if (sig.length !== 64)
|
|
63
|
+
throw new Error('A transaction signature is not 64 bytes');
|
|
64
|
+
unique.set(t.signature, { slot: t.slot, sig });
|
|
65
|
+
}
|
|
66
|
+
const ordered = [...unique.values()].sort((a, b) => a.slot - b.slot || Buffer.compare(a.sig, b.sig));
|
|
67
|
+
const tree = ordered.length === 0
|
|
68
|
+
? new Uint8Array(32)
|
|
69
|
+
: (0, log_1.rootOfLeaves)(ordered.map((t) => (0, sha256_1.sha256)(LEAF_TAG, u64le(BigInt(t.slot)), t.sig)));
|
|
70
|
+
return { root: (0, sha256_1.sha256)(ROOT_TAG, u64le(committedEpoch), u64le(BigInt(ordered.length)), tree), count: ordered.length };
|
|
71
|
+
}
|
|
72
|
+
async function collectProgramTxs(connection, programId, startSlot, endSlot) {
|
|
73
|
+
const found = new Map();
|
|
74
|
+
let before;
|
|
75
|
+
for (;;) {
|
|
76
|
+
const page = await connection.getSignaturesForAddress(programId, { before, limit: PAGE }, 'finalized');
|
|
77
|
+
for (const s of page) {
|
|
78
|
+
if (s.err === null && s.slot >= startSlot && s.slot < endSlot)
|
|
79
|
+
found.set(s.signature, { slot: s.slot, signature: s.signature });
|
|
80
|
+
}
|
|
81
|
+
if (page.length < PAGE || page[page.length - 1].slot < startSlot)
|
|
82
|
+
break;
|
|
83
|
+
before = page[page.length - 1].signature;
|
|
84
|
+
}
|
|
85
|
+
return [...found.values()];
|
|
86
|
+
}
|
|
87
|
+
async function indexerRoot(program, connection, submissionEpoch) {
|
|
88
|
+
if (submissionEpoch < 1n)
|
|
89
|
+
throw new Error('There is no epoch before the first for a root to commit to');
|
|
90
|
+
const committedEpoch = submissionEpoch - 1n;
|
|
91
|
+
const config = await program.account.protocolConfig.fetch((0, chain_1.configPda)(program.programId));
|
|
92
|
+
const { start, end } = epochSlotRange(BigInt(config.globalZgaEpochStartSlot.toString()), committedEpoch);
|
|
93
|
+
if (BigInt(await connection.getSlot('finalized')) < end)
|
|
94
|
+
throw new Error('The last slot of the epoch is not finalized yet');
|
|
95
|
+
const txs = await collectProgramTxs(connection, program.programId, Number(start), Number(end));
|
|
96
|
+
const { root, count } = epochRoot(committedEpoch, txs);
|
|
97
|
+
return { submissionEpoch, committedEpoch, root, entries: count, startSlot: start, endSlot: end };
|
|
98
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type ExplorerStatus = 'match' | 'mismatch' | 'unreachable' | 'undeclared' | 'none-published';
|
|
2
|
+
export interface ExplorerVerdict {
|
|
3
|
+
status: ExplorerStatus;
|
|
4
|
+
detail: string;
|
|
5
|
+
files?: number;
|
|
6
|
+
bad?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface CachedVerdict extends ExplorerVerdict {
|
|
9
|
+
consecutiveUnreachable: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ExplorerCheckOptions {
|
|
12
|
+
guard?: (url: string) => Promise<unknown>;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
maxManifestBytes?: number;
|
|
15
|
+
maxFiles?: number;
|
|
16
|
+
maxFileBytes?: number;
|
|
17
|
+
concurrency?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function verifyExplorer(baseUrl: string, expectedHex: string, opts?: ExplorerCheckOptions): Promise<ExplorerVerdict>;
|
|
20
|
+
export declare class ExplorerCheckCache {
|
|
21
|
+
private readonly settings;
|
|
22
|
+
private readonly entries;
|
|
23
|
+
private readonly pending;
|
|
24
|
+
constructor(settings?: {
|
|
25
|
+
matchTtlMs?: number;
|
|
26
|
+
otherTtlMs?: number;
|
|
27
|
+
now?: () => number;
|
|
28
|
+
});
|
|
29
|
+
check(key: string, baseUrl: string | null | undefined, expectedHex: string, opts?: ExplorerCheckOptions): Promise<CachedVerdict>;
|
|
30
|
+
}
|
|
31
|
+
export declare function explorerRefusal(v: ExplorerVerdict & {
|
|
32
|
+
consecutiveUnreachable?: number;
|
|
33
|
+
}, unreachableLimit?: number): string | null;
|
|
34
|
+
export interface ExplorerPolicy {
|
|
35
|
+
cache: ExplorerCheckCache;
|
|
36
|
+
expectedHex: () => Promise<string>;
|
|
37
|
+
unreachableLimit?: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function canonicalExplorerHashReader(program: any, ttlMs?: number, now?: () => number): () => Promise<string>;
|
|
40
|
+
export declare function fetchExplorerUrl(guildBase: string, guard?: (url: string) => Promise<unknown>): Promise<string | null>;
|
|
41
|
+
export declare function explorerGate(policy: ExplorerPolicy, guildKey: string, guildBase: string, guard?: (url: string) => Promise<unknown>): Promise<string | null>;
|
package/dist/explorer.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExplorerCheckCache = void 0;
|
|
4
|
+
exports.verifyExplorer = verifyExplorer;
|
|
5
|
+
exports.explorerRefusal = explorerRefusal;
|
|
6
|
+
exports.canonicalExplorerHashReader = canonicalExplorerHashReader;
|
|
7
|
+
exports.fetchExplorerUrl = fetchExplorerUrl;
|
|
8
|
+
exports.explorerGate = explorerGate;
|
|
9
|
+
const crypto_1 = require("crypto");
|
|
10
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
11
|
+
const HEX64 = /^[0-9a-f]{64}$/;
|
|
12
|
+
const sha256 = (bytes) => (0, crypto_1.createHash)('sha256').update(bytes).digest('hex');
|
|
13
|
+
class NetworkError extends Error {
|
|
14
|
+
}
|
|
15
|
+
async function read(url, max, opts) {
|
|
16
|
+
let target = url;
|
|
17
|
+
if (opts.guard) {
|
|
18
|
+
try {
|
|
19
|
+
const checked = await opts.guard(url);
|
|
20
|
+
if (typeof checked === 'string')
|
|
21
|
+
target = checked;
|
|
22
|
+
else if (checked instanceof URL)
|
|
23
|
+
target = checked.toString();
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
throw new NetworkError(`refused: ${err.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
let res;
|
|
30
|
+
try {
|
|
31
|
+
res = await fetch(target, { redirect: 'error', signal: AbortSignal.timeout(opts.timeoutMs ?? 20000) });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const why = String(err?.cause?.message ?? err.message);
|
|
35
|
+
if (/redirect/i.test(why))
|
|
36
|
+
throw new Error('the host redirects');
|
|
37
|
+
throw new NetworkError(why);
|
|
38
|
+
}
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
throw new Error(`HTTP ${res.status}`);
|
|
41
|
+
if (Number(res.headers.get('content-length') ?? 0) > max)
|
|
42
|
+
throw new Error(`larger than ${max} bytes`);
|
|
43
|
+
if (!res.body)
|
|
44
|
+
return Buffer.alloc(0);
|
|
45
|
+
const reader = res.body.getReader();
|
|
46
|
+
const chunks = [];
|
|
47
|
+
let size = 0;
|
|
48
|
+
for (;;) {
|
|
49
|
+
let step;
|
|
50
|
+
try {
|
|
51
|
+
step = await reader.read();
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
throw new NetworkError(err.message);
|
|
55
|
+
}
|
|
56
|
+
if (step.done)
|
|
57
|
+
break;
|
|
58
|
+
size += step.value.length;
|
|
59
|
+
if (size > max) {
|
|
60
|
+
await reader.cancel();
|
|
61
|
+
throw new Error(`larger than ${max} bytes`);
|
|
62
|
+
}
|
|
63
|
+
chunks.push(Buffer.from(step.value));
|
|
64
|
+
}
|
|
65
|
+
return Buffer.concat(chunks);
|
|
66
|
+
}
|
|
67
|
+
function parseManifest(text, maxFiles) {
|
|
68
|
+
const lines = text.split('\n');
|
|
69
|
+
if (lines.pop() !== '')
|
|
70
|
+
throw new Error('the manifest does not end with a newline');
|
|
71
|
+
if (lines.length === 0 || lines.length > maxFiles)
|
|
72
|
+
throw new Error(`the manifest lists ${lines.length} files`);
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
return lines.map((line) => {
|
|
75
|
+
const m = /^(.+) ([0-9a-f]{64})$/.exec(line);
|
|
76
|
+
if (!m)
|
|
77
|
+
throw new Error('an unusable manifest line');
|
|
78
|
+
const path = m[1];
|
|
79
|
+
if (path.length > 300 || path.startsWith('/') || path.includes('\\') || /[?#\u0000-\u001f]/.test(path) || path.split('/').some((s) => s === '' || s === '.' || s === '..')) {
|
|
80
|
+
throw new Error(`an unusable path: ${path.slice(0, 60)}`);
|
|
81
|
+
}
|
|
82
|
+
if (seen.has(path))
|
|
83
|
+
throw new Error('a path is listed twice');
|
|
84
|
+
seen.add(path);
|
|
85
|
+
return { path, hash: m[2] };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function verifyExplorer(baseUrl, expectedHex, opts = {}) {
|
|
89
|
+
const expected = expectedHex.toLowerCase();
|
|
90
|
+
if (!HEX64.test(expected))
|
|
91
|
+
throw new TypeError('the expected hash is not 64 hex characters');
|
|
92
|
+
if (/^0+$/.test(expected))
|
|
93
|
+
return { status: 'none-published', detail: 'no canonical explorer is published on chain' };
|
|
94
|
+
let base;
|
|
95
|
+
try {
|
|
96
|
+
base = new URL(baseUrl);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return { status: 'unreachable', detail: 'the explorer address is not a URL' };
|
|
100
|
+
}
|
|
101
|
+
if (base.protocol !== 'http:' && base.protocol !== 'https:')
|
|
102
|
+
return { status: 'unreachable', detail: 'the explorer address is not an http address' };
|
|
103
|
+
const root = `${base.origin}${base.pathname.replace(/\/*$/, '/')}`;
|
|
104
|
+
const at = (path) => root + path.split('/').map(encodeURIComponent).join('/');
|
|
105
|
+
let manifest;
|
|
106
|
+
try {
|
|
107
|
+
manifest = await read(at('build-manifest.txt'), opts.maxManifestBytes ?? 1048576, opts);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
if (err instanceof NetworkError)
|
|
111
|
+
return { status: 'unreachable', detail: err.message };
|
|
112
|
+
return { status: 'mismatch', detail: `no usable manifest: ${err.message}` };
|
|
113
|
+
}
|
|
114
|
+
if (sha256(manifest) !== expected)
|
|
115
|
+
return { status: 'mismatch', detail: 'the manifest is not the canonical one' };
|
|
116
|
+
let entries;
|
|
117
|
+
try {
|
|
118
|
+
entries = parseManifest(manifest.toString('utf8'), opts.maxFiles ?? 500);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
return { status: 'mismatch', detail: err.message };
|
|
122
|
+
}
|
|
123
|
+
const bad = [];
|
|
124
|
+
let network = 0;
|
|
125
|
+
let next = 0;
|
|
126
|
+
await Promise.all(Array.from({ length: opts.concurrency ?? 8 }, async () => {
|
|
127
|
+
for (;;) {
|
|
128
|
+
const entry = entries[next++];
|
|
129
|
+
if (!entry)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
if (sha256(await read(at(entry.path), opts.maxFileBytes ?? 52428800, opts)) !== entry.hash)
|
|
133
|
+
bad.push(`${entry.path}: the content differs`);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
bad.push(`${entry.path}: ${err.message}`);
|
|
137
|
+
if (err instanceof NetworkError)
|
|
138
|
+
network++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}));
|
|
142
|
+
bad.sort();
|
|
143
|
+
if (bad.length === 0)
|
|
144
|
+
return { status: 'match', detail: `all ${entries.length} files match`, files: entries.length };
|
|
145
|
+
const status = network === bad.length ? 'unreachable' : 'mismatch';
|
|
146
|
+
return { status, detail: `${bad.length} of ${entries.length} files failed`, files: entries.length, bad: bad.slice(0, 20) };
|
|
147
|
+
}
|
|
148
|
+
class ExplorerCheckCache {
|
|
149
|
+
constructor(settings = {}) {
|
|
150
|
+
this.settings = settings;
|
|
151
|
+
this.entries = new Map();
|
|
152
|
+
this.pending = new Map();
|
|
153
|
+
}
|
|
154
|
+
async check(key, baseUrl, expectedHex, opts = {}) {
|
|
155
|
+
const clock = this.settings.now ?? Date.now;
|
|
156
|
+
const expected = expectedHex.toLowerCase();
|
|
157
|
+
const url = baseUrl ?? '';
|
|
158
|
+
const have = this.entries.get(key);
|
|
159
|
+
const same = have !== undefined && have.expected === expected && have.url === url;
|
|
160
|
+
if (same) {
|
|
161
|
+
const ttl = have.verdict.status === 'match' ? (this.settings.matchTtlMs ?? 3600000) : (this.settings.otherTtlMs ?? 600000);
|
|
162
|
+
if (clock() - have.at < ttl)
|
|
163
|
+
return have.verdict;
|
|
164
|
+
}
|
|
165
|
+
const running = this.pending.get(key);
|
|
166
|
+
if (running)
|
|
167
|
+
return running;
|
|
168
|
+
const job = (async () => {
|
|
169
|
+
const verdict = url === '' ? { status: 'undeclared', detail: 'the guild declares no explorer' } : await verifyExplorer(url, expected, opts);
|
|
170
|
+
const previous = same ? have.verdict.consecutiveUnreachable : 0;
|
|
171
|
+
const cached = { ...verdict, consecutiveUnreachable: verdict.status === 'unreachable' ? previous + 1 : 0 };
|
|
172
|
+
this.entries.set(key, { at: clock(), expected, url, verdict: cached });
|
|
173
|
+
return cached;
|
|
174
|
+
})().finally(() => this.pending.delete(key));
|
|
175
|
+
this.pending.set(key, job);
|
|
176
|
+
return job;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.ExplorerCheckCache = ExplorerCheckCache;
|
|
180
|
+
function explorerRefusal(v, unreachableLimit = 3) {
|
|
181
|
+
switch (v.status) {
|
|
182
|
+
case 'match':
|
|
183
|
+
case 'none-published':
|
|
184
|
+
return null;
|
|
185
|
+
case 'mismatch':
|
|
186
|
+
return `the guild does not serve the canonical explorer: ${v.detail}`;
|
|
187
|
+
case 'undeclared':
|
|
188
|
+
return 'the guild declares no explorer';
|
|
189
|
+
case 'unreachable':
|
|
190
|
+
return (v.consecutiveUnreachable ?? 1) >= unreachableLimit ? `the guild's explorer has been unreachable for ${v.consecutiveUnreachable} checks: ${v.detail}` : null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function canonicalExplorerHashReader(program, ttlMs = 60000, now = Date.now) {
|
|
194
|
+
const config = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('config')], program.programId)[0];
|
|
195
|
+
let at = -Infinity;
|
|
196
|
+
let value = '';
|
|
197
|
+
return async () => {
|
|
198
|
+
if (value !== '' && now() - at < ttlMs)
|
|
199
|
+
return value;
|
|
200
|
+
const account = await program.account.protocolConfig.fetch(config);
|
|
201
|
+
value = Buffer.from(account.canonicalExplorerHash).toString('hex');
|
|
202
|
+
at = now();
|
|
203
|
+
return value;
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async function fetchExplorerUrl(guildBase, guard) {
|
|
207
|
+
try {
|
|
208
|
+
const body = JSON.parse((await read(`${guildBase.replace(/\/+$/, '')}/v1/info`, 4096, { guard, timeoutMs: 8000 })).toString('utf8'));
|
|
209
|
+
const url = body?.explorerUrl;
|
|
210
|
+
return typeof url === 'string' && url.length > 0 && url.length <= 300 ? url : null;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function explorerGate(policy, guildKey, guildBase, guard) {
|
|
217
|
+
const expected = await policy.expectedHex();
|
|
218
|
+
if (/^0+$/.test(expected))
|
|
219
|
+
return null;
|
|
220
|
+
const url = await fetchExplorerUrl(guildBase, guard);
|
|
221
|
+
return explorerRefusal(await policy.cache.check(guildKey, url, expected, { guard }), policy.unreachableLimit);
|
|
222
|
+
}
|
package/dist/gossip.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const GOSSIP_SEAL_OVERHEAD = 48;
|
|
2
|
+
export declare function x25519PublicFromEd25519(publicKey: Uint8Array): Buffer;
|
|
3
|
+
export declare function sealGossipPayload(plaintext: Uint8Array, recipientWallet: Uint8Array): Buffer;
|
|
4
|
+
export declare function openGossipPayload(sealed: Uint8Array, walletSecretKey: Uint8Array): Buffer;
|
package/dist/gossip.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GOSSIP_SEAL_OVERHEAD = void 0;
|
|
4
|
+
exports.x25519PublicFromEd25519 = x25519PublicFromEd25519;
|
|
5
|
+
exports.sealGossipPayload = sealGossipPayload;
|
|
6
|
+
exports.openGossipPayload = openGossipPayload;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
exports.GOSSIP_SEAL_OVERHEAD = 48;
|
|
9
|
+
const P = (1n << 255n) - 19n;
|
|
10
|
+
const mod = (a) => ((a % P) + P) % P;
|
|
11
|
+
function pow(base, exponent) {
|
|
12
|
+
let result = 1n;
|
|
13
|
+
let b = mod(base);
|
|
14
|
+
let e = exponent;
|
|
15
|
+
while (e > 0n) {
|
|
16
|
+
if (e & 1n)
|
|
17
|
+
result = mod(result * b);
|
|
18
|
+
b = mod(b * b);
|
|
19
|
+
e >>= 1n;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
const inverse = (a) => pow(a, P - 2n);
|
|
24
|
+
const SPKI_PREFIX = Buffer.from('302a300506032b656e032100', 'hex');
|
|
25
|
+
const PKCS8_PREFIX = Buffer.from('302e020100300506032b656e04220420', 'hex');
|
|
26
|
+
const ZERO_NONCE = Buffer.alloc(12);
|
|
27
|
+
const publicKeyObject = (raw) => (0, crypto_1.createPublicKey)({ key: Buffer.concat([SPKI_PREFIX, raw]), format: 'der', type: 'spki' });
|
|
28
|
+
const privateKeyObject = (raw) => (0, crypto_1.createPrivateKey)({ key: Buffer.concat([PKCS8_PREFIX, raw]), format: 'der', type: 'pkcs8' });
|
|
29
|
+
function symmetricKey(shared, ephemeralPublic, recipientPublic) {
|
|
30
|
+
return Buffer.from((0, crypto_1.hkdfSync)('sha256', shared, Buffer.concat([ephemeralPublic, recipientPublic]), 'zga-gossip-sealed-v1', 32));
|
|
31
|
+
}
|
|
32
|
+
function x25519PublicFromEd25519(publicKey) {
|
|
33
|
+
const bytes = Buffer.from(publicKey);
|
|
34
|
+
bytes[31] &= 0x7f;
|
|
35
|
+
let y = 0n;
|
|
36
|
+
for (let i = 31; i >= 0; i--)
|
|
37
|
+
y = (y << 8n) | BigInt(bytes[i]);
|
|
38
|
+
let u = mod((1n + y) * inverse(mod(1n - y)));
|
|
39
|
+
const out = Buffer.alloc(32);
|
|
40
|
+
for (let i = 0; i < 32; i++) {
|
|
41
|
+
out[i] = Number(u & 0xffn);
|
|
42
|
+
u >>= 8n;
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function x25519SecretFromEd25519Seed(seed) {
|
|
47
|
+
const h = (0, crypto_1.createHash)('sha512').update(seed).digest().subarray(0, 32);
|
|
48
|
+
h[0] &= 248;
|
|
49
|
+
h[31] &= 127;
|
|
50
|
+
h[31] |= 64;
|
|
51
|
+
return Buffer.from(h);
|
|
52
|
+
}
|
|
53
|
+
function sealGossipPayload(plaintext, recipientWallet) {
|
|
54
|
+
const recipientPublic = x25519PublicFromEd25519(recipientWallet);
|
|
55
|
+
const ephemeral = (0, crypto_1.generateKeyPairSync)('x25519');
|
|
56
|
+
const ephemeralPublic = ephemeral.publicKey.export({ format: 'der', type: 'spki' }).subarray(-32);
|
|
57
|
+
const shared = (0, crypto_1.diffieHellman)({ privateKey: ephemeral.privateKey, publicKey: publicKeyObject(recipientPublic) });
|
|
58
|
+
const cipher = (0, crypto_1.createCipheriv)('chacha20-poly1305', symmetricKey(shared, ephemeralPublic, recipientPublic), ZERO_NONCE, { authTagLength: 16 });
|
|
59
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
60
|
+
return Buffer.concat([ephemeralPublic, ciphertext, cipher.getAuthTag()]);
|
|
61
|
+
}
|
|
62
|
+
function openGossipPayload(sealed, walletSecretKey) {
|
|
63
|
+
if (sealed.length < exports.GOSSIP_SEAL_OVERHEAD)
|
|
64
|
+
throw new Error('A sealed payload is at least 48 bytes');
|
|
65
|
+
if (walletSecretKey.length !== 64)
|
|
66
|
+
throw new Error('The wallet secret key is the 64 bytes of a keypair');
|
|
67
|
+
const bytes = Buffer.from(sealed);
|
|
68
|
+
const ephemeralPublic = bytes.subarray(0, 32);
|
|
69
|
+
const tag = bytes.subarray(bytes.length - 16);
|
|
70
|
+
const ciphertext = bytes.subarray(32, bytes.length - 16);
|
|
71
|
+
const secret = x25519SecretFromEd25519Seed(walletSecretKey.subarray(0, 32));
|
|
72
|
+
const recipientPublic = x25519PublicFromEd25519(walletSecretKey.subarray(32, 64));
|
|
73
|
+
const shared = (0, crypto_1.diffieHellman)({ privateKey: privateKeyObject(secret), publicKey: publicKeyObject(ephemeralPublic) });
|
|
74
|
+
const decipher = (0, crypto_1.createDecipheriv)('chacha20-poly1305', symmetricKey(shared, ephemeralPublic, recipientPublic), ZERO_NONCE, { authTagLength: 16 });
|
|
75
|
+
decipher.setAuthTag(tag);
|
|
76
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
77
|
+
}
|
package/dist/idl.d.ts
ADDED
package/dist/idl.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fieldOffset = fieldOffset;
|
|
4
|
+
exports.variantIndex = variantIndex;
|
|
5
|
+
const FIXED = {
|
|
6
|
+
bool: 1, u8: 1, i8: 1, u16: 2, i16: 2, u32: 4, i32: 4, f32: 4, u64: 8, i64: 8, f64: 8, u128: 16, i128: 16, pubkey: 32,
|
|
7
|
+
};
|
|
8
|
+
function typeDef(idl, name) {
|
|
9
|
+
const def = (idl.types ?? []).find((t) => t.name === name);
|
|
10
|
+
if (!def)
|
|
11
|
+
throw new Error(`The IDL has no type ${name}`);
|
|
12
|
+
return def;
|
|
13
|
+
}
|
|
14
|
+
function sizeOf(idl, type) {
|
|
15
|
+
if (typeof type === 'string') {
|
|
16
|
+
if (type in FIXED)
|
|
17
|
+
return FIXED[type];
|
|
18
|
+
throw new Error(`The size of ${type} is not fixed`);
|
|
19
|
+
}
|
|
20
|
+
if (type.array)
|
|
21
|
+
return sizeOf(idl, type.array[0]) * type.array[1];
|
|
22
|
+
if (type.defined) {
|
|
23
|
+
const def = typeDef(idl, type.defined.name);
|
|
24
|
+
if (def.type.kind === 'enum') {
|
|
25
|
+
if (def.type.variants.some((v) => v.fields && v.fields.length > 0)) {
|
|
26
|
+
throw new Error(`${def.name} carries data, so its size is not fixed`);
|
|
27
|
+
}
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
return (def.type.fields ?? []).reduce((n, f) => n + sizeOf(idl, f.type), 0);
|
|
31
|
+
}
|
|
32
|
+
throw new Error('The size of this type is not fixed');
|
|
33
|
+
}
|
|
34
|
+
function fieldOffset(idl, account, field) {
|
|
35
|
+
const def = typeDef(idl, account);
|
|
36
|
+
if (def.type.kind !== 'struct')
|
|
37
|
+
throw new Error(`${account} is not a struct`);
|
|
38
|
+
let offset = 8;
|
|
39
|
+
for (const f of def.type.fields ?? []) {
|
|
40
|
+
if (f.name === field)
|
|
41
|
+
return offset;
|
|
42
|
+
offset += sizeOf(idl, f.type);
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`${account} has no field ${field}`);
|
|
45
|
+
}
|
|
46
|
+
function variantIndex(idl, enumName, variant) {
|
|
47
|
+
const def = typeDef(idl, enumName);
|
|
48
|
+
const index = (def.type.variants ?? []).findIndex((v) => v.name.toLowerCase() === variant.toLowerCase());
|
|
49
|
+
if (index < 0)
|
|
50
|
+
throw new Error(`${enumName} has no variant ${variant}`);
|
|
51
|
+
return index;
|
|
52
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export * from './config';
|
|
2
|
+
export * from './chain';
|
|
3
|
+
export * from './addresses';
|
|
4
|
+
export * from './token';
|
|
5
|
+
export * from './receipt';
|
|
6
|
+
export * from './signer';
|
|
7
|
+
export * from './lookupTable';
|
|
8
|
+
export * from './settlement';
|
|
9
|
+
export * from './session';
|
|
10
|
+
export * from './idl';
|
|
11
|
+
export * from './sha256';
|
|
12
|
+
export * from './log';
|
|
13
|
+
export * from './epoch-root';
|
|
14
|
+
export * from './replay';
|
|
15
|
+
export * from './crank/runner';
|
|
16
|
+
export * from './crank/prices';
|
|
17
|
+
export * from './crank/gate';
|
|
18
|
+
export * from './crank/shieldFees';
|
|
19
|
+
export * from './crank/lending';
|
|
20
|
+
export * from './lending';
|
|
21
|
+
export * from './gossip';
|
|
22
|
+
export * from './relayer/settings';
|
|
23
|
+
export * from './relayer/limits';
|
|
24
|
+
export * from './relayer/errors';
|
|
25
|
+
export * from './relayer/relayer';
|
|
26
|
+
export * from './relayer/chain';
|
|
27
|
+
export * from './relayer/server';
|
|
28
|
+
export * from './billing';
|
|
29
|
+
export * from './observer';
|
|
30
|
+
export * from './explorer';
|
|
31
|
+
export * from './shieldLimits';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./config"), exports);
|
|
18
|
+
__exportStar(require("./chain"), exports);
|
|
19
|
+
__exportStar(require("./addresses"), exports);
|
|
20
|
+
__exportStar(require("./token"), exports);
|
|
21
|
+
__exportStar(require("./receipt"), exports);
|
|
22
|
+
__exportStar(require("./signer"), exports);
|
|
23
|
+
__exportStar(require("./lookupTable"), exports);
|
|
24
|
+
__exportStar(require("./settlement"), exports);
|
|
25
|
+
__exportStar(require("./session"), exports);
|
|
26
|
+
__exportStar(require("./idl"), exports);
|
|
27
|
+
__exportStar(require("./sha256"), exports);
|
|
28
|
+
__exportStar(require("./log"), exports);
|
|
29
|
+
__exportStar(require("./epoch-root"), exports);
|
|
30
|
+
__exportStar(require("./replay"), exports);
|
|
31
|
+
__exportStar(require("./crank/runner"), exports);
|
|
32
|
+
__exportStar(require("./crank/prices"), exports);
|
|
33
|
+
__exportStar(require("./crank/gate"), exports);
|
|
34
|
+
__exportStar(require("./crank/shieldFees"), exports);
|
|
35
|
+
__exportStar(require("./crank/lending"), exports);
|
|
36
|
+
__exportStar(require("./lending"), exports);
|
|
37
|
+
__exportStar(require("./gossip"), exports);
|
|
38
|
+
__exportStar(require("./relayer/settings"), exports);
|
|
39
|
+
__exportStar(require("./relayer/limits"), exports);
|
|
40
|
+
__exportStar(require("./relayer/errors"), exports);
|
|
41
|
+
__exportStar(require("./relayer/relayer"), exports);
|
|
42
|
+
__exportStar(require("./relayer/chain"), exports);
|
|
43
|
+
__exportStar(require("./relayer/server"), exports);
|
|
44
|
+
__exportStar(require("./billing"), exports);
|
|
45
|
+
__exportStar(require("./observer"), exports);
|
|
46
|
+
__exportStar(require("./explorer"), exports);
|
|
47
|
+
__exportStar(require("./shieldLimits"), exports);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
|
|
2
|
+
export declare const LENDING_PAY_ALL = 18446744073709551615n;
|
|
3
|
+
export declare function lendingVaultAddresses(pid: PublicKey, creator: PublicKey, nonce: bigint | number): {
|
|
4
|
+
vault: PublicKey;
|
|
5
|
+
cash: PublicKey;
|
|
6
|
+
collateral: PublicKey;
|
|
7
|
+
};
|
|
8
|
+
export declare const lendingLenderAddress: (pid: PublicKey, vault: PublicKey, lender: PublicKey) => PublicKey;
|
|
9
|
+
export declare const lendingLoanAddress: (pid: PublicKey, vault: PublicKey, borrower: PublicKey, nonce: bigint | number) => PublicKey;
|
|
10
|
+
export declare const lendingFeeAddress: (pid: PublicKey) => PublicKey;
|
|
11
|
+
export declare function ensureAssociatedAccountInstruction(payer: PublicKey, owner: PublicKey, mint: PublicKey): TransactionInstruction;
|
|
12
|
+
export interface CreateVaultOptions {
|
|
13
|
+
creator: PublicKey;
|
|
14
|
+
nonce: bigint;
|
|
15
|
+
collateralAssetId: number;
|
|
16
|
+
collateralMint: PublicKey;
|
|
17
|
+
borrowIsZga: boolean;
|
|
18
|
+
zgaMint: PublicKey;
|
|
19
|
+
collateralRatioBps: number;
|
|
20
|
+
liquidationThresholdBps: number;
|
|
21
|
+
interestRateBps: number;
|
|
22
|
+
originationFeeBps: number;
|
|
23
|
+
maxLoanDurationDays: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function createLendingVault(program: any, o: CreateVaultOptions): any;
|
|
26
|
+
export declare function depositLendingLiquidity(program: any, o: {
|
|
27
|
+
vault: PublicKey;
|
|
28
|
+
lender: PublicKey;
|
|
29
|
+
lenderTokenAccount: PublicKey;
|
|
30
|
+
amount: bigint;
|
|
31
|
+
minShares: bigint;
|
|
32
|
+
authority?: PublicKey;
|
|
33
|
+
}): any;
|
|
34
|
+
export declare function withdrawLendingLiquidity(program: any, o: {
|
|
35
|
+
vault: PublicKey;
|
|
36
|
+
lender: PublicKey;
|
|
37
|
+
lenderTokenAccount: PublicKey;
|
|
38
|
+
shares: bigint;
|
|
39
|
+
minAmount: bigint;
|
|
40
|
+
authority?: PublicKey;
|
|
41
|
+
}): Promise<any>;
|
|
42
|
+
export declare function openLendingLoan(program: any, o: {
|
|
43
|
+
vault: PublicKey;
|
|
44
|
+
borrower: PublicKey;
|
|
45
|
+
loanNonce: bigint;
|
|
46
|
+
collateralAmount: bigint;
|
|
47
|
+
principalAmount: bigint;
|
|
48
|
+
durationDays: number;
|
|
49
|
+
borrowerCollateralAccount: PublicKey;
|
|
50
|
+
borrowerBorrowAccount: PublicKey;
|
|
51
|
+
zgaMint: PublicKey;
|
|
52
|
+
authority?: PublicKey;
|
|
53
|
+
}): Promise<any>;
|
|
54
|
+
export declare function repayLendingLoan(program: any, o: {
|
|
55
|
+
vault: PublicKey;
|
|
56
|
+
borrower: PublicKey;
|
|
57
|
+
loanNonce: bigint;
|
|
58
|
+
amount: bigint;
|
|
59
|
+
borrowerCollateralAccount: PublicKey;
|
|
60
|
+
borrowerBorrowAccount: PublicKey;
|
|
61
|
+
authority?: PublicKey;
|
|
62
|
+
}): any;
|
|
63
|
+
export declare function addLendingCollateral(program: any, o: {
|
|
64
|
+
vault: PublicKey;
|
|
65
|
+
borrower: PublicKey;
|
|
66
|
+
loanNonce: bigint;
|
|
67
|
+
amount: bigint;
|
|
68
|
+
borrowerCollateralAccount: PublicKey;
|
|
69
|
+
authority?: PublicKey;
|
|
70
|
+
}): any;
|
|
71
|
+
export declare function extendLendingLoan(program: any, o: {
|
|
72
|
+
vault: PublicKey;
|
|
73
|
+
borrower: PublicKey;
|
|
74
|
+
loanNonce: bigint;
|
|
75
|
+
durationDays: number;
|
|
76
|
+
borrowerBorrowAccount: PublicKey;
|
|
77
|
+
authority?: PublicKey;
|
|
78
|
+
}): Promise<any>;
|
|
79
|
+
export declare function liquidateLendingLoan(program: any, o: {
|
|
80
|
+
loan: PublicKey;
|
|
81
|
+
liquidator: PublicKey;
|
|
82
|
+
}): Promise<any>;
|