@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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/dist/addresses.d.ts +29 -0
  3. package/dist/addresses.js +43 -0
  4. package/dist/billing.d.ts +41 -0
  5. package/dist/billing.js +141 -0
  6. package/dist/bin/crank.d.ts +2 -0
  7. package/dist/bin/crank.js +34 -0
  8. package/dist/bin/relayer.d.ts +2 -0
  9. package/dist/bin/relayer.js +37 -0
  10. package/dist/chain.d.ts +14 -0
  11. package/dist/chain.js +48 -0
  12. package/dist/config.d.ts +11 -0
  13. package/dist/config.js +61 -0
  14. package/dist/crank/gate.d.ts +2 -0
  15. package/dist/crank/gate.js +26 -0
  16. package/dist/crank/lending.d.ts +16 -0
  17. package/dist/crank/lending.js +144 -0
  18. package/dist/crank/prices.d.ts +11 -0
  19. package/dist/crank/prices.js +177 -0
  20. package/dist/crank/runner.d.ts +15 -0
  21. package/dist/crank/runner.js +30 -0
  22. package/dist/crank/shieldFees.d.ts +15 -0
  23. package/dist/crank/shieldFees.js +161 -0
  24. package/dist/epoch-root.d.ts +23 -0
  25. package/dist/epoch-root.js +98 -0
  26. package/dist/explorer.d.ts +41 -0
  27. package/dist/explorer.js +222 -0
  28. package/dist/gossip.d.ts +4 -0
  29. package/dist/gossip.js +77 -0
  30. package/dist/idl.d.ts +2 -0
  31. package/dist/idl.js +52 -0
  32. package/dist/index.d.ts +31 -0
  33. package/dist/index.js +47 -0
  34. package/dist/lending.d.ts +82 -0
  35. package/dist/lending.js +164 -0
  36. package/dist/log.d.ts +27 -0
  37. package/dist/log.js +173 -0
  38. package/dist/lookupTable.d.ts +2 -0
  39. package/dist/lookupTable.js +47 -0
  40. package/dist/observer.d.ts +59 -0
  41. package/dist/observer.js +255 -0
  42. package/dist/receipt.d.ts +30 -0
  43. package/dist/receipt.js +27 -0
  44. package/dist/relayer/chain.d.ts +35 -0
  45. package/dist/relayer/chain.js +171 -0
  46. package/dist/relayer/errors.d.ts +8 -0
  47. package/dist/relayer/errors.js +29 -0
  48. package/dist/relayer/limits.d.ts +15 -0
  49. package/dist/relayer/limits.js +47 -0
  50. package/dist/relayer/relayer.d.ts +25 -0
  51. package/dist/relayer/relayer.js +54 -0
  52. package/dist/relayer/server.d.ts +8 -0
  53. package/dist/relayer/server.js +101 -0
  54. package/dist/relayer/settings.d.ts +23 -0
  55. package/dist/relayer/settings.js +69 -0
  56. package/dist/replay.d.ts +13 -0
  57. package/dist/replay.js +118 -0
  58. package/dist/session.d.ts +29 -0
  59. package/dist/session.js +31 -0
  60. package/dist/settlement.d.ts +17 -0
  61. package/dist/settlement.js +96 -0
  62. package/dist/sha256.d.ts +3 -0
  63. package/dist/sha256.js +83 -0
  64. package/dist/shieldLimits.d.ts +15 -0
  65. package/dist/shieldLimits.js +24 -0
  66. package/dist/signer.d.ts +5 -0
  67. package/dist/signer.js +43 -0
  68. package/dist/token.d.ts +6 -0
  69. package/dist/token.js +33 -0
  70. package/package.json +36 -4
  71. package/README.md +0 -3
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isLoanLiquidatable = isLoanLiquidatable;
4
+ exports.lendingFeesTask = lendingFeesTask;
5
+ exports.lendingLiquidationTask = lendingLiquidationTask;
6
+ const web3_js_1 = require("@solana/web3.js");
7
+ const anchor_1 = require("@coral-xyz/anchor");
8
+ const chain_1 = require("../chain");
9
+ const lending_1 = require("../lending");
10
+ const token_1 = require("../token");
11
+ const LOAN_DISCRIMINATOR = [68, 228, 171, 3, 182, 214, 23, 125];
12
+ const E9 = 1000000000n;
13
+ const BPS = 10000n;
14
+ const YEAR_SECS = 31536000n;
15
+ const GRACE_SECS = 604800n;
16
+ const big = (x) => BigInt(x.toString());
17
+ const short = (e) => String(e?.message ?? e).slice(0, 160);
18
+ const cu = (units) => [web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units })];
19
+ function isLoanLiquidatable(f) {
20
+ if (f.now > f.dueAt + GRACE_SECS)
21
+ return true;
22
+ const age = f.now > f.accruedAt ? f.now - f.accruedAt : 0n;
23
+ const owed = f.principal + (f.principal * f.rateBps * age) / (BPS * YEAR_SECS);
24
+ const debtUsd = (owed * f.borrowPriceE9 + E9 - 1n) / E9;
25
+ if (debtUsd === 0n)
26
+ return false;
27
+ const collateralUsd = (f.collateralAmount * f.collateralScaleTo9 * f.collateralPriceE9) / E9;
28
+ return (collateralUsd * BPS) / debtUsd <= f.thresholdBps;
29
+ }
30
+ function lendingPdas(pid) {
31
+ const b = (s) => Buffer.from(s);
32
+ return {
33
+ lpLock: (0, chain_1.lpLockPda)(pid),
34
+ stakingVault: (0, chain_1.pda)(pid, b('staking_vault')),
35
+ stakingPool: (0, chain_1.pda)(pid, b('staking_pool')),
36
+ zgapReserve: (0, chain_1.pda)(pid, b('zgap_reserve')),
37
+ zgapMint: (0, chain_1.pda)(pid, b('zgap_mint')),
38
+ zgapAuthority: (0, chain_1.pda)(pid, b('zgap_authority')),
39
+ collateralVault: (0, chain_1.pda)(pid, b('zgap_collateral_vault')),
40
+ solVault: (0, chain_1.pda)(pid, b('zgap_sol_vault')),
41
+ reserveSol: (0, chain_1.pda)(pid, b('zgap_reserve_sol')),
42
+ };
43
+ }
44
+ function lendingFeesTask(everyMs = 3600000) {
45
+ return {
46
+ name: 'lending-fees',
47
+ everyMs,
48
+ async run(ctx) {
49
+ const pid = ctx.program.programId;
50
+ const p = lendingPdas(pid);
51
+ const feeAccount = (0, lending_1.lendingFeeAddress)(pid);
52
+ const held = await ctx.connection.getAccountInfo(feeAccount);
53
+ if (!held)
54
+ return;
55
+ const amount = held.data.readBigUInt64LE(64);
56
+ if (amount === 0n)
57
+ return;
58
+ const lock = await ctx.connection.getAccountInfo(p.lpLock);
59
+ if (!lock)
60
+ return;
61
+ const zgaMint = new web3_js_1.PublicKey(lock.data.subarray(0, 32));
62
+ await ctx.program.methods.routeZnlZgapFees().accountsPartial({
63
+ protocolRedeem: { zgaPriceCache: (0, chain_1.cachePda)(pid, chain_1.ZGA_ASSET_ID), solPriceCache: (0, chain_1.cachePda)(pid, chain_1.SOL_ASSET_ID), solVault: p.solVault, reserveSolAccount: p.reserveSol },
64
+ zgapAuthority: p.zgapAuthority, zgapMint: p.zgapMint, collateralVault: p.collateralVault, feeAccount,
65
+ stakingVault: p.stakingVault, stakingPool: p.stakingPool, lpLockVault: p.lpLock,
66
+ zgapReserve: p.zgapReserve, reserveZgaAccount: (0, token_1.associatedTokenAddress)(zgaMint, p.zgapReserve), tokenProgram: token_1.TOKEN_PROGRAM_ID,
67
+ }).preInstructions(cu(400000)).rpc();
68
+ ctx.log(`lending: ${amount} ZGAP of the protocol's cut routed to the staking yield`);
69
+ },
70
+ };
71
+ }
72
+ async function readCache(ctx, pid, assetId) {
73
+ const info = await ctx.connection.getAccountInfo((0, chain_1.cachePda)(pid, assetId));
74
+ if (!info)
75
+ return null;
76
+ const c = (0, chain_1.decodeAccount)(ctx.program, 'PriceCacheAccount', info.data).cache;
77
+ return { twap: big(c.twapE9), latest: big(c.latestE9) };
78
+ }
79
+ function lendingLiquidationTask(everyMs = 60000) {
80
+ const unaffordable = new Set();
81
+ return {
82
+ name: 'lending-liquidations',
83
+ everyMs,
84
+ async run(ctx) {
85
+ const pid = ctx.program.programId;
86
+ const accounts = await ctx.connection.getProgramAccounts(pid, {
87
+ filters: [{ memcmp: { offset: 0, bytes: anchor_1.utils.bytes.bs58.encode(Buffer.from(LOAN_DISCRIMINATOR)) } }],
88
+ });
89
+ if (accounts.length === 0)
90
+ return;
91
+ const now = BigInt((await ctx.connection.getBlockTime(await ctx.connection.getSlot())) ?? Math.floor(Date.now() / 1000));
92
+ const vaults = new Map();
93
+ const caches = new Map();
94
+ const cacheOf = async (id) => {
95
+ if (!caches.has(id))
96
+ caches.set(id, await readCache(ctx, pid, id));
97
+ return caches.get(id) ?? null;
98
+ };
99
+ for (const { pubkey, account } of accounts) {
100
+ try {
101
+ const loan = (0, chain_1.decodeAccount)(ctx.program, 'LendingLoan', account.data);
102
+ const vaultKey = loan.vault.toBase58();
103
+ if (!vaults.has(vaultKey))
104
+ vaults.set(vaultKey, await ctx.program.account.lendingVault.fetch(loan.vault));
105
+ const vault = vaults.get(vaultKey);
106
+ const collateralCache = await cacheOf(Number(vault.collateralAssetId));
107
+ const zgaCache = await cacheOf(chain_1.ZGA_ASSET_ID);
108
+ if (!collateralCache || !zgaCache)
109
+ continue;
110
+ const facts = {
111
+ principal: big(loan.principal),
112
+ collateralAmount: big(loan.collateralAmount),
113
+ accruedAt: big(loan.accruedAt),
114
+ dueAt: big(loan.dueAt),
115
+ rateBps: big(vault.interestRateBps),
116
+ thresholdBps: big(vault.liquidationThresholdBps),
117
+ collateralScaleTo9: big(vault.collateralScaleTo9),
118
+ collateralPriceE9: collateralCache.twap < collateralCache.latest ? collateralCache.twap : collateralCache.latest,
119
+ borrowPriceE9: vault.borrowIsZga ? (zgaCache.twap > zgaCache.latest ? zgaCache.twap : zgaCache.latest) : E9,
120
+ now,
121
+ };
122
+ if (!isLoanLiquidatable(facts))
123
+ continue;
124
+ const owed = facts.principal + (facts.principal * facts.rateBps * (now > facts.accruedAt ? now - facts.accruedAt : 0n)) / (BPS * YEAR_SECS);
125
+ const held = await ctx.connection.getTokenAccountBalance((0, token_1.associatedTokenAddress)(vault.borrowMint, ctx.caller.publicKey)).catch(() => null);
126
+ if (!held || BigInt(held.value.amount) < owed) {
127
+ if (!unaffordable.has(pubkey.toBase58())) {
128
+ unaffordable.add(pubkey.toBase58());
129
+ ctx.log(`loan ${pubkey.toBase58()} can be liquidated but the caller holds less than the ${owed} it owes in the borrowed asset`);
130
+ }
131
+ continue;
132
+ }
133
+ const builder = await (0, lending_1.liquidateLendingLoan)(ctx.program, { loan: pubkey, liquidator: ctx.caller.publicKey });
134
+ await builder.preInstructions([...cu(400000)], true).rpc();
135
+ unaffordable.delete(pubkey.toBase58());
136
+ ctx.log(`loan ${pubkey.toBase58()} liquidated`);
137
+ }
138
+ catch (e) {
139
+ ctx.log(`loan ${pubkey.toBase58()} not liquidated: ${short(e)}`);
140
+ }
141
+ }
142
+ },
143
+ };
144
+ }
@@ -0,0 +1,11 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import { CrankTask } from './runner';
3
+ export interface Feed {
4
+ assetId: number;
5
+ address: PublicKey;
6
+ data: any;
7
+ }
8
+ export declare function quoteAssets(d: any): number[];
9
+ export declare function inDependencyOrder(feeds: Feed[]): Feed[];
10
+ export declare function postDue(now: number, lastTs: number | null, spacing: number, minPostSecs: number): boolean;
11
+ export declare function priceFeedsTask(everyMs?: number, minPostSecs?: number): CrankTask;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.quoteAssets = quoteAssets;
4
+ exports.inDependencyOrder = inDependencyOrder;
5
+ exports.postDue = postDue;
6
+ exports.priceFeedsTask = priceFeedsTask;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ const chain_1 = require("../chain");
9
+ const MAX_ASSETS = 32;
10
+ const SOURCE_SIGNED = 0;
11
+ const SOURCE_POOL = 1;
12
+ const SOURCE_DLMM = 2;
13
+ const SOURCE_FIXED = 3;
14
+ const SOURCE_AGGREGATED = 4;
15
+ const SOURCE_DLMM_AGGREGATED = 5;
16
+ const short = (e) => String(e?.message ?? e).slice(0, 160);
17
+ const cu = (units) => [web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units })];
18
+ async function loadFeeds(ctx) {
19
+ const pid = ctx.program.programId;
20
+ const addresses = Array.from({ length: MAX_ASSETS }, (_, id) => (0, chain_1.feedPda)(pid, id));
21
+ const infos = await ctx.connection.getMultipleAccountsInfo(addresses);
22
+ const feeds = [];
23
+ infos.forEach((info, id) => {
24
+ if (!info || !info.owner.equals(pid))
25
+ return;
26
+ feeds.push({ assetId: id, address: addresses[id], data: (0, chain_1.decodeAccount)(ctx.program, 'PriceFeedAccount', info.data) });
27
+ });
28
+ return feeds;
29
+ }
30
+ function quoteAssets(d) {
31
+ if (d.sourceKind === SOURCE_POOL || d.sourceKind === SOURCE_DLMM)
32
+ return [d.quoteAssetId];
33
+ if (d.sourceKind === SOURCE_AGGREGATED)
34
+ return [d.quoteAssetId, d.legs[0].quoteAssetId, d.legs[1].quoteAssetId];
35
+ if (d.sourceKind === SOURCE_DLMM_AGGREGATED)
36
+ return [d.quoteAssetId, d.legs[0].quoteAssetId];
37
+ return [];
38
+ }
39
+ function inDependencyOrder(feeds) {
40
+ const byAsset = new Map(feeds.map((f) => [f.assetId, f]));
41
+ const depth = new Map();
42
+ const walk = (id, seen) => {
43
+ if (depth.has(id))
44
+ return depth.get(id);
45
+ const f = byAsset.get(id);
46
+ if (!f || seen.has(id))
47
+ return 0;
48
+ seen.add(id);
49
+ const d = 1 + Math.max(-1, ...quoteAssets(f.data).map((q) => walk(q, seen)));
50
+ depth.set(id, quoteAssets(f.data).length === 0 ? 0 : d);
51
+ return depth.get(id);
52
+ };
53
+ feeds.forEach((f) => walk(f.assetId, new Set()));
54
+ return [...feeds].sort((a, b) => (depth.get(a.assetId) ?? 0) - (depth.get(b.assetId) ?? 0) || a.assetId - b.assetId);
55
+ }
56
+ function latestSample(d) {
57
+ if (d.sampleCount === 0)
58
+ return null;
59
+ const s = d.samples[d.cursor === 0 ? d.samples.length - 1 : d.cursor - 1];
60
+ return { ts: Number(s.ts.toString()) };
61
+ }
62
+ async function updateFeed(ctx, f) {
63
+ const pid = ctx.program.programId;
64
+ const d = f.data;
65
+ const m = ctx.program.methods;
66
+ switch (d.sourceKind) {
67
+ case SOURCE_FIXED:
68
+ await m.updatePriceFeedFixed().accountsPartial({ feed: f.address }).rpc();
69
+ return true;
70
+ case SOURCE_AGGREGATED:
71
+ await m.updatePriceFeedAggregated().accountsPartial({
72
+ feed: f.address,
73
+ poolA: d.pool, vaultQuoteA: d.vaultQuote, quoteCacheA: (0, chain_1.cachePda)(pid, d.quoteAssetId),
74
+ poolB: d.legs[0].pool, vaultQuoteB: d.legs[0].vaultQuote, quoteCacheB: (0, chain_1.cachePda)(pid, d.legs[0].quoteAssetId),
75
+ poolC: d.legs[1].pool, vaultQuoteC: d.legs[1].vaultQuote, quoteCacheC: (0, chain_1.cachePda)(pid, d.legs[1].quoteAssetId),
76
+ }).preInstructions(cu(400000)).rpc();
77
+ return true;
78
+ case SOURCE_DLMM_AGGREGATED:
79
+ await m.updatePriceFeedDlmmAggregated().accountsPartial({
80
+ feed: f.address,
81
+ lbPairA: d.pool, vaultQuoteA: d.vaultQuote, quoteCacheA: (0, chain_1.cachePda)(pid, d.quoteAssetId),
82
+ lbPairB: d.legs[0].pool, vaultQuoteB: d.legs[0].vaultQuote, quoteCacheB: (0, chain_1.cachePda)(pid, d.legs[0].quoteAssetId),
83
+ }).preInstructions(cu(400000)).rpc();
84
+ return true;
85
+ default:
86
+ return false;
87
+ }
88
+ }
89
+ function postDue(now, lastTs, spacing, minPostSecs) {
90
+ if (lastTs === null)
91
+ return true;
92
+ return now - lastTs >= Math.max(spacing + 2, minPostSecs);
93
+ }
94
+ async function updateInstruction(ctx, f) {
95
+ const pid = ctx.program.programId;
96
+ const d = f.data;
97
+ const m = ctx.program.methods;
98
+ switch (d.sourceKind) {
99
+ case SOURCE_FIXED:
100
+ return m.updatePriceFeedFixed().accountsPartial({ feed: f.address }).instruction();
101
+ case SOURCE_AGGREGATED:
102
+ return m.updatePriceFeedAggregated().accountsPartial({
103
+ feed: f.address,
104
+ poolA: d.pool, vaultQuoteA: d.vaultQuote, quoteCacheA: (0, chain_1.cachePda)(pid, d.quoteAssetId),
105
+ poolB: d.legs[0].pool, vaultQuoteB: d.legs[0].vaultQuote, quoteCacheB: (0, chain_1.cachePda)(pid, d.legs[0].quoteAssetId),
106
+ poolC: d.legs[1].pool, vaultQuoteC: d.legs[1].vaultQuote, quoteCacheC: (0, chain_1.cachePda)(pid, d.legs[1].quoteAssetId),
107
+ }).instruction();
108
+ case SOURCE_DLMM_AGGREGATED:
109
+ return m.updatePriceFeedDlmmAggregated().accountsPartial({
110
+ feed: f.address,
111
+ lbPairA: d.pool, vaultQuoteA: d.vaultQuote, quoteCacheA: (0, chain_1.cachePda)(pid, d.quoteAssetId),
112
+ lbPairB: d.legs[0].pool, vaultQuoteB: d.legs[0].vaultQuote, quoteCacheB: (0, chain_1.cachePda)(pid, d.legs[0].quoteAssetId),
113
+ }).instruction();
114
+ default:
115
+ return null;
116
+ }
117
+ }
118
+ async function postAndCrank(ctx, f) {
119
+ const pid = ctx.program.programId;
120
+ try {
121
+ const update = await updateInstruction(ctx, f);
122
+ if (!update)
123
+ return false;
124
+ const crank = await ctx.program.methods.crankPriceCache().accountsPartial({ cache: (0, chain_1.cachePda)(pid, f.assetId), feed: f.address }).instruction();
125
+ const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 800000 }), update, crank);
126
+ await ctx.program.provider.sendAndConfirm(tx);
127
+ ctx.log(`asset ${f.assetId}: sample added and cache brought up to it`);
128
+ return true;
129
+ }
130
+ catch (e) {
131
+ ctx.log(`asset ${f.assetId}: the one-transaction post failed, trying two steps: ${short(e)}`);
132
+ return false;
133
+ }
134
+ }
135
+ async function crankCacheIfBehind(ctx, f) {
136
+ const pid = ctx.program.programId;
137
+ const cacheAddress = (0, chain_1.cachePda)(pid, f.assetId);
138
+ const cache = await ctx.program.account.priceCacheAccount.fetchNullable(cacheAddress);
139
+ if (!cache)
140
+ return;
141
+ const feed = await ctx.program.account.priceFeedAccount.fetch(f.address);
142
+ const newest = latestSample(feed);
143
+ if (!newest || newest.ts <= Number(cache.cache.updatedAt.toString()))
144
+ return;
145
+ try {
146
+ await ctx.program.methods.crankPriceCache().accountsPartial({ cache: cacheAddress, feed: f.address }).preInstructions(cu(400000)).rpc();
147
+ ctx.log(`asset ${f.assetId}: cache brought up to the newest sample`);
148
+ }
149
+ catch (e) {
150
+ ctx.log(`asset ${f.assetId}: cache not cranked: ${short(e)}`);
151
+ }
152
+ }
153
+ function priceFeedsTask(everyMs = 30000, minPostSecs = 0) {
154
+ return {
155
+ name: 'price-feeds',
156
+ everyMs,
157
+ async run(ctx) {
158
+ for (const f of inDependencyOrder(await loadFeeds(ctx))) {
159
+ const now = await (0, chain_1.chainTime)(ctx.connection);
160
+ const last = latestSample(f.data);
161
+ const spacing = Number(f.data.minSampleSpacingSecs);
162
+ if (postDue(now, last ? last.ts : null, spacing, minPostSecs)) {
163
+ if (await postAndCrank(ctx, f))
164
+ continue;
165
+ try {
166
+ if (await updateFeed(ctx, f))
167
+ ctx.log(`asset ${f.assetId}: sample added`);
168
+ }
169
+ catch (e) {
170
+ ctx.log(`asset ${f.assetId}: sample not added: ${short(e)}`);
171
+ }
172
+ }
173
+ await crankCacheIfBehind(ctx, f);
174
+ }
175
+ },
176
+ };
177
+ }
@@ -0,0 +1,15 @@
1
+ import { Connection, Keypair } from '@solana/web3.js';
2
+ export interface CrankContext {
3
+ program: any;
4
+ connection: Connection;
5
+ caller: Keypair;
6
+ log: (message: string) => void;
7
+ }
8
+ export interface CrankTask {
9
+ name: string;
10
+ everyMs: number;
11
+ run(ctx: CrankContext): Promise<void>;
12
+ }
13
+ export declare function runTasks(ctx: CrankContext, tasks: CrankTask[], opts?: {
14
+ once?: boolean;
15
+ }): Promise<void>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runTasks = runTasks;
4
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
5
+ async function runTasks(ctx, tasks, opts = {}) {
6
+ const lastRun = new Map();
7
+ const attempt = async (task) => {
8
+ try {
9
+ await task.run(ctx);
10
+ }
11
+ catch (e) {
12
+ ctx.log(`${task.name}: ${String(e?.message ?? e).slice(0, 200)}`);
13
+ }
14
+ };
15
+ if (opts.once) {
16
+ for (const task of tasks)
17
+ await attempt(task);
18
+ return;
19
+ }
20
+ for (;;) {
21
+ const now = Date.now();
22
+ for (const task of tasks) {
23
+ if (now - (lastRun.get(task.name) ?? 0) < task.everyMs)
24
+ continue;
25
+ lastRun.set(task.name, now);
26
+ await attempt(task);
27
+ }
28
+ await sleep(1000);
29
+ }
30
+ }
@@ -0,0 +1,15 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import { CrankTask } from './runner';
3
+ export declare const NATIVE_MINT: PublicKey;
4
+ export type AssetKind = 'zga' | 'sol' | 'zgap' | 'sink';
5
+ export type Route = 'zga' | 'sol' | 'zgap-nop-tax' | 'zgap-fees' | 'sink';
6
+ export interface FeesDue {
7
+ protocol: bigint;
8
+ unattributed: bigint;
9
+ }
10
+ export declare function classifyAsset(mint: PublicKey, mints: {
11
+ zga: PublicKey;
12
+ zgap: PublicKey;
13
+ }, sink?: PublicKey | null): AssetKind | null;
14
+ export declare function routesFor(kind: AssetKind, due: FeesDue): Route[];
15
+ export declare function shieldFeesTask(everyMs?: number): CrankTask;
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NATIVE_MINT = void 0;
4
+ exports.classifyAsset = classifyAsset;
5
+ exports.routesFor = routesFor;
6
+ exports.shieldFeesTask = shieldFeesTask;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ const chain_1 = require("../chain");
9
+ const token_1 = require("../token");
10
+ const MAX_ASSETS = 32;
11
+ exports.NATIVE_MINT = new web3_js_1.PublicKey('So11111111111111111111111111111111111111112');
12
+ const TOKEN_ACCOUNT_BYTES = 165;
13
+ const INITIALIZE_ACCOUNT_3 = 18;
14
+ const big = (x) => BigInt(x.toString());
15
+ const short = (e) => String(e?.message ?? e).slice(0, 160);
16
+ const cu = (units) => [web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units })];
17
+ function classifyAsset(mint, mints, sink = null) {
18
+ if (sink && !sink.equals(web3_js_1.PublicKey.default))
19
+ return 'sink';
20
+ if (mint.equals(mints.zga))
21
+ return 'zga';
22
+ if (mint.equals(exports.NATIVE_MINT))
23
+ return 'sol';
24
+ if (mint.equals(mints.zgap))
25
+ return 'zgap';
26
+ return null;
27
+ }
28
+ function routesFor(kind, due) {
29
+ const any = due.protocol > 0n || due.unattributed > 0n;
30
+ switch (kind) {
31
+ case 'zga':
32
+ return any ? ['zga'] : [];
33
+ case 'sol':
34
+ return any ? ['sol'] : [];
35
+ case 'zgap': {
36
+ const routes = [];
37
+ if (due.unattributed > 0n)
38
+ routes.push('zgap-nop-tax');
39
+ if (due.protocol > 0n)
40
+ routes.push('zgap-fees');
41
+ return routes;
42
+ }
43
+ case 'sink':
44
+ return any ? ['sink'] : [];
45
+ default:
46
+ return [];
47
+ }
48
+ }
49
+ function shieldPdas(pid) {
50
+ const b = (s) => Buffer.from(s);
51
+ return {
52
+ lpLock: (0, chain_1.lpLockPda)(pid),
53
+ asset: (id) => (0, chain_1.pda)(pid, b('shield_asset'), Buffer.from([id])),
54
+ vault: (id) => (0, chain_1.pda)(pid, b('shield_vault'), Buffer.from([id])),
55
+ nopRewardPool: (0, chain_1.pda)(pid, b('nop_reward_pool')),
56
+ stakingVault: (0, chain_1.pda)(pid, b('staking_vault')),
57
+ stakingPool: (0, chain_1.pda)(pid, b('staking_pool')),
58
+ zgapReserve: (0, chain_1.pda)(pid, b('zgap_reserve')),
59
+ zgapMint: (0, chain_1.pda)(pid, b('zgap_mint')),
60
+ zgapAuthority: (0, chain_1.pda)(pid, b('zgap_authority')),
61
+ collateralVault: (0, chain_1.pda)(pid, b('zgap_collateral_vault')),
62
+ solVault: (0, chain_1.pda)(pid, b('zgap_sol_vault')),
63
+ reserveSol: (0, chain_1.pda)(pid, b('zgap_reserve_sol')),
64
+ };
65
+ }
66
+ function initializeWrappedSolAccount(account, owner) {
67
+ return new web3_js_1.TransactionInstruction({
68
+ programId: token_1.TOKEN_PROGRAM_ID,
69
+ keys: [
70
+ { pubkey: account, isSigner: false, isWritable: true },
71
+ { pubkey: exports.NATIVE_MINT, isSigner: false, isWritable: false },
72
+ ],
73
+ data: Buffer.concat([Buffer.from([INITIALIZE_ACCOUNT_3]), owner.toBuffer()]),
74
+ });
75
+ }
76
+ async function send(ctx, assetId, route, zgaMint, sink) {
77
+ const pid = ctx.program.programId;
78
+ const p = shieldPdas(pid);
79
+ const m = ctx.program.methods;
80
+ const common = { assetConfig: p.asset(assetId), vault: p.vault(assetId), caller: ctx.caller.publicKey, tokenProgram: token_1.TOKEN_PROGRAM_ID };
81
+ switch (route) {
82
+ case 'sink':
83
+ await m.routeShieldFeesToSink(assetId).accountsPartial({ assetConfig: p.asset(assetId), vault: p.vault(assetId), sinkAccount: sink, tokenProgram: token_1.TOKEN_PROGRAM_ID }).rpc();
84
+ return;
85
+ case 'zga':
86
+ await m.routeShieldFees(assetId).accountsPartial({
87
+ ...common, nopRewardPool: null, stakingVault: p.stakingVault, stakingPool: p.stakingPool, zgapReserve: p.zgapReserve,
88
+ wsolTransit: null, reserveZgaAccount: (0, token_1.associatedTokenAddress)(zgaMint, p.zgapReserve),
89
+ }).rpc();
90
+ return;
91
+ case 'sol': {
92
+ const transit = web3_js_1.Keypair.generate();
93
+ const rent = await ctx.connection.getMinimumBalanceForRentExemption(TOKEN_ACCOUNT_BYTES);
94
+ const create = web3_js_1.SystemProgram.createAccount({
95
+ fromPubkey: ctx.caller.publicKey, newAccountPubkey: transit.publicKey, lamports: rent, space: TOKEN_ACCOUNT_BYTES, programId: token_1.TOKEN_PROGRAM_ID,
96
+ });
97
+ const route = await m.routeShieldFees(assetId).accountsPartial({
98
+ ...common, nopRewardPool: null, stakingVault: null, stakingPool: null, zgapReserve: p.zgapReserve,
99
+ wsolTransit: transit.publicKey, reserveZgaAccount: null,
100
+ }).instruction();
101
+ await ctx.program.provider.sendAndConfirm(new web3_js_1.Transaction().add(create, initializeWrappedSolAccount(transit.publicKey, ctx.caller.publicKey), route), [transit]);
102
+ return;
103
+ }
104
+ case 'zgap-nop-tax':
105
+ await m.routeShieldFees(assetId).accountsPartial({
106
+ ...common, nopRewardPool: p.nopRewardPool, stakingVault: null, stakingPool: null, zgapReserve: null, wsolTransit: null, reserveZgaAccount: null,
107
+ }).rpc();
108
+ return;
109
+ case 'zgap-fees':
110
+ await m.routeShieldZgapFees(assetId).accountsPartial({
111
+ assetConfig: p.asset(assetId), vault: p.vault(assetId),
112
+ protocolRedeem: { zgaPriceCache: (0, chain_1.cachePda)(pid, chain_1.ZGA_ASSET_ID), solPriceCache: (0, chain_1.cachePda)(pid, chain_1.SOL_ASSET_ID), solVault: p.solVault, reserveSolAccount: p.reserveSol },
113
+ zgapAuthority: p.zgapAuthority, zgapMint: p.zgapMint, collateralVault: p.collateralVault, stakingVault: p.stakingVault, stakingPool: p.stakingPool,
114
+ lpLockVault: p.lpLock, zgapReserve: p.zgapReserve, reserveZgaAccount: (0, token_1.associatedTokenAddress)(zgaMint, p.zgapReserve), tokenProgram: token_1.TOKEN_PROGRAM_ID,
115
+ }).preInstructions(cu(400000)).rpc();
116
+ return;
117
+ default:
118
+ return;
119
+ }
120
+ }
121
+ function shieldFeesTask(everyMs = 3600000) {
122
+ const unknown = new Set();
123
+ return {
124
+ name: 'shield-fees',
125
+ everyMs,
126
+ async run(ctx) {
127
+ const pid = ctx.program.programId;
128
+ const p = shieldPdas(pid);
129
+ const lock = await ctx.connection.getAccountInfo(p.lpLock);
130
+ if (!lock)
131
+ return;
132
+ const mints = { zga: new web3_js_1.PublicKey(lock.data.subarray(0, 32)), zgap: p.zgapMint };
133
+ const addresses = Array.from({ length: MAX_ASSETS }, (_, id) => p.asset(id));
134
+ const infos = await ctx.connection.getMultipleAccountsInfo(addresses);
135
+ for (let id = 0; id < infos.length; id++) {
136
+ const info = infos[id];
137
+ if (!info || !info.owner.equals(pid))
138
+ continue;
139
+ const asset = (0, chain_1.decodeAccount)(ctx.program, 'ShieldAssetConfig', info.data);
140
+ const sink = asset.feeSink ?? web3_js_1.PublicKey.default;
141
+ const kind = classifyAsset(asset.mint, mints, sink);
142
+ if (!kind) {
143
+ if (!unknown.has(id)) {
144
+ unknown.add(id);
145
+ ctx.log(`shield asset ${id}: no routing is known for its mint and governance has named no sink for it, so its fees are left`);
146
+ }
147
+ continue;
148
+ }
149
+ for (const route of routesFor(kind, { protocol: big(asset.feesProtocol), unattributed: big(asset.feesUnattributed) })) {
150
+ try {
151
+ await send(ctx, id, route, mints.zga, sink);
152
+ ctx.log(`shield asset ${id}: ${route} fees routed`);
153
+ }
154
+ catch (e) {
155
+ ctx.log(`shield asset ${id}: ${route} fees not routed: ${short(e)}`);
156
+ }
157
+ }
158
+ }
159
+ },
160
+ };
161
+ }
@@ -0,0 +1,23 @@
1
+ import { Connection, PublicKey } from '@solana/web3.js';
2
+ export interface ProgramTx {
3
+ slot: number;
4
+ signature: string;
5
+ }
6
+ export interface IndexerRoot {
7
+ submissionEpoch: bigint;
8
+ committedEpoch: bigint;
9
+ root: Uint8Array;
10
+ entries: number;
11
+ startSlot: bigint;
12
+ endSlot: bigint;
13
+ }
14
+ export declare function epochSlotRange(configStartSlot: bigint, epoch: bigint, epochSlots?: bigint): {
15
+ start: bigint;
16
+ end: bigint;
17
+ };
18
+ export declare function epochRoot(committedEpoch: bigint, txs: ProgramTx[]): {
19
+ root: Uint8Array;
20
+ count: number;
21
+ };
22
+ export declare function collectProgramTxs(connection: Connection, programId: PublicKey, startSlot: number, endSlot: number): Promise<ProgramTx[]>;
23
+ export declare function indexerRoot(program: any, connection: Connection, submissionEpoch: bigint): Promise<IndexerRoot>;