@voidifydao/sdk 1.0.0 → 2.0.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/README.md +0 -0
- package/dist/cli/config/init.js +1 -7
- package/dist/cli/config/loader.js +0 -1
- package/dist/cli/config/types.d.ts +0 -1
- package/dist/cli/deposit.js +45 -29
- package/dist/cli/helpers.js +0 -3
- package/dist/cli/progress.d.ts +5 -0
- package/dist/cli/progress.js +37 -0
- package/dist/cli/relayer.js +16 -7
- package/dist/cli/withdraw.js +20 -6
- package/dist/context.d.ts +1 -4
- package/dist/context.js +14 -12
- package/dist/idl/voidify/idl.d.ts +1 -1
- package/dist/idl/voidify/idl.json +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +0 -2
- package/dist/relayer/server/server.js +29 -13
- package/dist/relayer/server/switchboard.js +15 -6
- package/dist/relayer/types.d.ts +1 -0
- package/dist/substream/chain/index.d.ts +10 -3
- package/dist/substream/chain/index.js +14 -7
- package/dist/substream/chain/registry.d.ts +2 -2
- package/dist/substream/chain/utils.d.ts +2 -1
- package/dist/substream/chain/utils.js +35 -11
- package/dist/substream/client.d.ts +6 -1
- package/dist/substream/database/indexeddb.js +3 -0
- package/dist/substream/database/sqlite.d.ts +1 -0
- package/dist/substream/database/sqlite.js +6 -0
- package/dist/substream/modules/deposit.d.ts +2 -1
- package/dist/substream/modules/deposit.js +24 -20
- package/dist/substream/modules/relayer.d.ts +2 -1
- package/dist/substream/modules/relayer.js +39 -33
- package/dist/substream/runtime.d.ts +19 -4
- package/dist/substream/runtime.js +216 -16
- package/dist/substream/server/server.d.ts +2 -0
- package/dist/substream/server/server.js +42 -8
- package/dist/substream/types.d.ts +21 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +1 -1
- package/dist/voidify/deposit.d.ts +2 -0
- package/dist/voidify/deposit.js +1 -1
- package/dist/voidify/program.d.ts +0 -4
- package/dist/voidify/program.js +0 -10
- package/dist/voidify/relayer/list.d.ts +2 -0
- package/dist/voidify/relayer/list.js +1 -1
- package/dist/voidify/withdraw.d.ts +7 -2
- package/dist/voidify/withdraw.js +68 -10
- package/package.json +5 -4
- package/dist/idl/voidify-staking/idl.d.ts +0 -93
- package/dist/idl/voidify-staking/idl.js +0 -1
- package/dist/idl/voidify-staking/idl.json +0 -87
- package/dist/staking/commands.d.ts +0 -3
- package/dist/staking/commands.js +0 -13
- package/dist/staking/index.d.ts +0 -2
- package/dist/staking/index.js +0 -2
- package/dist/staking/program.d.ts +0 -18
- package/dist/staking/program.js +0 -40
- package/dist/types/errors.d.ts +0 -1
- package/dist/types/errors.js +0 -16
package/README.md
ADDED
|
File without changes
|
package/dist/cli/config/init.js
CHANGED
|
@@ -13,7 +13,6 @@ export function buildTemplate(type) {
|
|
|
13
13
|
return {
|
|
14
14
|
rpcUrl: defaults.rpcUrl,
|
|
15
15
|
programId: REQUIRED("voidify program ID"),
|
|
16
|
-
stakingProgramId: null,
|
|
17
16
|
keypair: fileKeypairPlaceholder("user keypair"),
|
|
18
17
|
substream: { ...defaults.substream },
|
|
19
18
|
proof: { ...defaults.proof },
|
|
@@ -22,7 +21,6 @@ export function buildTemplate(type) {
|
|
|
22
21
|
return {
|
|
23
22
|
rpcUrl: defaults.rpcUrl,
|
|
24
23
|
programId: REQUIRED("voidify program ID"),
|
|
25
|
-
stakingProgramId: null,
|
|
26
24
|
keypair: fileKeypairPlaceholder("relayer keypair"),
|
|
27
25
|
relayerServer: { ...defaults.relayerServer },
|
|
28
26
|
};
|
|
@@ -37,7 +35,6 @@ export function buildTemplate(type) {
|
|
|
37
35
|
return {
|
|
38
36
|
rpcUrl: defaults.rpcUrl,
|
|
39
37
|
programId: REQUIRED("voidify program ID"),
|
|
40
|
-
stakingProgramId: null,
|
|
41
38
|
keypair: fileKeypairPlaceholder("keypair for this config"),
|
|
42
39
|
substream: { ...defaults.substream },
|
|
43
40
|
proof: { ...defaults.proof },
|
|
@@ -53,7 +50,6 @@ export function postInitHint(type) {
|
|
|
53
50
|
"Next steps:",
|
|
54
51
|
" - programId <- voidify main program",
|
|
55
52
|
" - keypair.path <- your keypair JSON file path",
|
|
56
|
-
" - stakingProgramId <- optional staking program ID",
|
|
57
53
|
" (substream / proof already use defaults; no changes needed)",
|
|
58
54
|
"",
|
|
59
55
|
"Applicable commands: deposit / withdraw / note.",
|
|
@@ -63,8 +59,7 @@ export function postInitHint(type) {
|
|
|
63
59
|
"Next steps:",
|
|
64
60
|
" - programId <- voidify main program",
|
|
65
61
|
" - keypair.path <- keypair for the relayer service itself",
|
|
66
|
-
" - relayerServer.feedId
|
|
67
|
-
" - stakingProgramId <- optional staking program ID",
|
|
62
|
+
" - relayerServer.feedId ← Switchboard on-demand feed ID",
|
|
68
63
|
"",
|
|
69
64
|
"Applicable commands: relayer start / relayer list.",
|
|
70
65
|
].join("\n");
|
|
@@ -80,7 +75,6 @@ export function postInitHint(type) {
|
|
|
80
75
|
return [
|
|
81
76
|
"Next steps: full template; fill in values as needed.",
|
|
82
77
|
" required: programId, keypair.path, relayerServer.feedId",
|
|
83
|
-
" optional: stakingProgramId",
|
|
84
78
|
"",
|
|
85
79
|
"Reminder: one config should use one keypair. Create separate config files and switch with -c when roles differ.",
|
|
86
80
|
].join("\n");
|
package/dist/cli/deposit.js
CHANGED
|
@@ -3,27 +3,66 @@ import { deposit, listDeposits } from "../voidify/deposit.js";
|
|
|
3
3
|
import { parseUnits } from "../utils/amount.js";
|
|
4
4
|
import { Note } from "../utils/note.js";
|
|
5
5
|
import { createServiceContext, SOL_DECIMALS, } from "../cli/helpers.js";
|
|
6
|
+
import { createCliProgressBar } from "../cli/progress.js";
|
|
6
7
|
export function registerDepositCommands(program) {
|
|
7
8
|
const depositCommand = new Command("deposit")
|
|
8
9
|
.enablePositionalOptions()
|
|
10
|
+
.argument("<amount>", "Deposit amount in UI units, such as 1 for 1 SOL")
|
|
9
11
|
.description("Deposit commands");
|
|
12
|
+
depositCommand.option("-c, --commitment <string>", "Commitment generated by the note gen command; if omitted, a new one is generated and printed");
|
|
10
13
|
depositCommand
|
|
11
|
-
.command("
|
|
12
|
-
.
|
|
13
|
-
.
|
|
14
|
-
.
|
|
14
|
+
.command("list <amount>")
|
|
15
|
+
.description("List records for a denomination pool, using UI units such as 1 for 1 SOL")
|
|
16
|
+
.option("--offset <number>", "Pagination offset")
|
|
17
|
+
.option("--limit <number>", "Page size")
|
|
18
|
+
.option("-o, --output <file>", "Write output to a file")
|
|
19
|
+
.action(async (amount, options) => {
|
|
20
|
+
try {
|
|
21
|
+
const ctx = await createServiceContext(program.opts());
|
|
22
|
+
const progress = createCliProgressBar("Sync deposits");
|
|
23
|
+
const deposits = await (async () => {
|
|
24
|
+
try {
|
|
25
|
+
return await listDeposits(ctx, parseUnits(amount, SOL_DECIMALS), {
|
|
26
|
+
offset: options.offset !== undefined
|
|
27
|
+
? parseInt(options.offset)
|
|
28
|
+
: undefined,
|
|
29
|
+
limit: options.limit !== undefined
|
|
30
|
+
? parseInt(options.limit)
|
|
31
|
+
: undefined,
|
|
32
|
+
output: options.output,
|
|
33
|
+
sync: { reporter: progress },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
progress.finish();
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
if (!options.output) {
|
|
41
|
+
console.log("Deposit records:", deposits);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error("List deposits failed:", error);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
depositCommand.action(async (amount, options) => {
|
|
50
|
+
if (!amount) {
|
|
51
|
+
depositCommand.help({ error: true });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
15
54
|
try {
|
|
16
55
|
const ctx = await createServiceContext(program.opts());
|
|
17
56
|
let commitment = options.commitment;
|
|
18
57
|
if (!commitment) {
|
|
19
|
-
const note = await Note.generate(
|
|
58
|
+
const note = await Note.generate(amount);
|
|
20
59
|
commitment = note.commitment;
|
|
21
60
|
console.log("Note generated:", {
|
|
22
61
|
note: note.serialize(),
|
|
23
62
|
commitment: note.commitment,
|
|
24
63
|
});
|
|
25
64
|
}
|
|
26
|
-
const txSignature = await deposit(ctx, commitment, parseUnits(
|
|
65
|
+
const txSignature = await deposit(ctx, commitment, parseUnits(amount, SOL_DECIMALS));
|
|
27
66
|
console.log("Transaction successful:", { txSignature });
|
|
28
67
|
}
|
|
29
68
|
catch (error) {
|
|
@@ -31,28 +70,5 @@ export function registerDepositCommands(program) {
|
|
|
31
70
|
process.exit(1);
|
|
32
71
|
}
|
|
33
72
|
});
|
|
34
|
-
depositCommand
|
|
35
|
-
.command("list <amount>")
|
|
36
|
-
.description("List records for a denomination pool, using UI units such as 1 for 1 SOL")
|
|
37
|
-
.option("--offset <number>", "Pagination offset")
|
|
38
|
-
.option("--limit <number>", "Page size")
|
|
39
|
-
.option("-o, --output <file>", "Write output to a file")
|
|
40
|
-
.action(async (amount, options) => {
|
|
41
|
-
try {
|
|
42
|
-
const ctx = await createServiceContext(program.opts());
|
|
43
|
-
const deposits = await listDeposits(ctx, parseUnits(amount, SOL_DECIMALS), {
|
|
44
|
-
offset: options.offset !== undefined
|
|
45
|
-
? parseInt(options.offset)
|
|
46
|
-
: undefined,
|
|
47
|
-
limit: options.limit !== undefined ? parseInt(options.limit) : undefined,
|
|
48
|
-
output: options.output,
|
|
49
|
-
});
|
|
50
|
-
console.log("Deposit records:", deposits);
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
console.error("List deposits failed:", error);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
73
|
program.addCommand(depositCommand);
|
|
58
74
|
}
|
package/dist/cli/helpers.js
CHANGED
|
@@ -39,9 +39,6 @@ export async function contextFromConfig(cfg, cliKeypairPath) {
|
|
|
39
39
|
return new Context({
|
|
40
40
|
rpcUrl: cfg.rpcUrl,
|
|
41
41
|
programId: cfg.programId ? new PublicKey(cfg.programId) : undefined,
|
|
42
|
-
stakingProgramId: cfg.stakingProgramId
|
|
43
|
-
? new PublicKey(cfg.stakingProgramId)
|
|
44
|
-
: undefined,
|
|
45
42
|
wallet: keypair,
|
|
46
43
|
substream,
|
|
47
44
|
wasmPath: cfg.proof ? expandHome(cfg.proof.wasmPath) : undefined,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function createCliProgressBar(label) {
|
|
2
|
+
if (!process.stderr.isTTY) {
|
|
3
|
+
return {
|
|
4
|
+
update() { },
|
|
5
|
+
finish() { },
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
let started = false;
|
|
9
|
+
let lastLength = 0;
|
|
10
|
+
function render(status) {
|
|
11
|
+
const progress = status.progress;
|
|
12
|
+
if (!progress)
|
|
13
|
+
return;
|
|
14
|
+
const width = 28;
|
|
15
|
+
const ratio = progress.total > 0 ? progress.current / progress.total : 1;
|
|
16
|
+
const complete = Math.min(width, Math.floor(ratio * width));
|
|
17
|
+
const bar = `${"#".repeat(complete)}${"-".repeat(width - complete)}`;
|
|
18
|
+
const percent = Math.floor(ratio * 100)
|
|
19
|
+
.toString()
|
|
20
|
+
.padStart(3, " ");
|
|
21
|
+
const text = `${label} [${bar}] ${percent}% ${progress.current}/${progress.total}`;
|
|
22
|
+
const padding = " ".repeat(Math.max(0, lastLength - text.length));
|
|
23
|
+
process.stderr.write(`\r${text}${padding}`);
|
|
24
|
+
started = true;
|
|
25
|
+
lastLength = text.length;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
update: render,
|
|
29
|
+
finish() {
|
|
30
|
+
if (!started)
|
|
31
|
+
return;
|
|
32
|
+
process.stderr.write("\n");
|
|
33
|
+
started = false;
|
|
34
|
+
lastLength = 0;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/cli/relayer.js
CHANGED
|
@@ -2,21 +2,30 @@ import { Command } from "commander";
|
|
|
2
2
|
import { startRelayer } from "../relayer/server/index.js";
|
|
3
3
|
import { listRelayers } from "../voidify/relayer/list.js";
|
|
4
4
|
import { createServiceContext, contextFromConfig, loadCliConfig, } from "../cli/helpers.js";
|
|
5
|
+
import { createCliProgressBar } from "../cli/progress.js";
|
|
5
6
|
export function registerRelayerCommands(program) {
|
|
6
7
|
const relayerCommand = new Command("relayer")
|
|
7
8
|
.enablePositionalOptions()
|
|
8
9
|
.description("Relayer commands");
|
|
9
10
|
relayerCommand
|
|
10
|
-
.command("list")
|
|
11
|
-
.description("List relayer information
|
|
12
|
-
.option("--pubkey <pubkey>", "Specific relayer public key")
|
|
11
|
+
.command("list [pubkey]")
|
|
12
|
+
.description("List relayer information for a specific relayer")
|
|
13
13
|
.option("-o, --output <file>", "Write output to a file")
|
|
14
|
-
.action(async (options) => {
|
|
14
|
+
.action(async (pubkey, options) => {
|
|
15
15
|
try {
|
|
16
16
|
const ctx = await createServiceContext(program.opts());
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const progress = createCliProgressBar("Sync relayers");
|
|
18
|
+
const relayers = await (async () => {
|
|
19
|
+
try {
|
|
20
|
+
return await listRelayers(ctx, pubkey, {
|
|
21
|
+
output: options.output,
|
|
22
|
+
sync: { reporter: progress },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
progress.finish();
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
20
29
|
console.log("Relayer records:", relayers);
|
|
21
30
|
}
|
|
22
31
|
catch (error) {
|
package/dist/cli/withdraw.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { withdraw } from "../voidify/withdraw.js";
|
|
2
2
|
import { createServiceContext } from "../cli/helpers.js";
|
|
3
|
+
import { createCliProgressBar } from "../cli/progress.js";
|
|
3
4
|
export function registerWithdrawCommands(program) {
|
|
4
5
|
program
|
|
5
6
|
.command("withdraw")
|
|
6
7
|
.description("Withdraw through a relayer")
|
|
7
|
-
.
|
|
8
|
-
.
|
|
9
|
-
.
|
|
10
|
-
.option("--
|
|
11
|
-
.action(async (options) => {
|
|
8
|
+
.argument("<note>", "Note generated when depositing")
|
|
9
|
+
.option("--recipient <pubkey>", "Recipient address")
|
|
10
|
+
.option("--relayer <name>", "Relayer name")
|
|
11
|
+
.option("--send-rpc", "Send this client's RPC URL to the relayer for this withdraw request")
|
|
12
|
+
.action(async (note, options) => {
|
|
12
13
|
try {
|
|
13
14
|
const ctx = await createServiceContext(program.opts());
|
|
14
|
-
const
|
|
15
|
+
const depositProgress = createCliProgressBar("Sync deposits");
|
|
16
|
+
const relayerProgress = createCliProgressBar("Sync relayers");
|
|
17
|
+
const result = await (async () => {
|
|
18
|
+
try {
|
|
19
|
+
return await withdraw(ctx, note, options.recipient, options.relayer, {
|
|
20
|
+
depositSync: { reporter: depositProgress },
|
|
21
|
+
relayerSync: { reporter: relayerProgress },
|
|
22
|
+
}, options.sendRpc ?? false);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
depositProgress.finish();
|
|
26
|
+
relayerProgress.finish();
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
15
29
|
console.log("Withdraw successful:", { txSignature: result });
|
|
16
30
|
process.exit(0);
|
|
17
31
|
}
|
package/dist/context.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export type SubstreamConfig = {
|
|
|
17
17
|
export interface ContextOptions {
|
|
18
18
|
rpcUrl?: string;
|
|
19
19
|
programId?: PublicKey;
|
|
20
|
-
stakingProgramId?: PublicKey;
|
|
21
20
|
wallet?: anchor.Wallet | Keypair | null;
|
|
22
21
|
substream?: SubstreamConfig;
|
|
23
22
|
wasmPath?: string;
|
|
@@ -27,17 +26,15 @@ export declare class Context {
|
|
|
27
26
|
private readonly _rpcUrl;
|
|
28
27
|
private readonly _connection;
|
|
29
28
|
private readonly _programId;
|
|
30
|
-
private readonly _stakingProgramId;
|
|
31
29
|
private readonly _substream;
|
|
32
30
|
private readonly _wasmPath;
|
|
33
31
|
private readonly _zkeyPath;
|
|
34
32
|
readonly wallet: anchor.Wallet | null;
|
|
35
33
|
constructor(opts?: ContextOptions);
|
|
34
|
+
withRpcUrl(rpcUrl: string): Context;
|
|
36
35
|
get rpcUrl(): string;
|
|
37
36
|
get connection(): Connection;
|
|
38
37
|
get programId(): PublicKey;
|
|
39
|
-
get stakingProgramId(): PublicKey;
|
|
40
|
-
hasStakingProgramId(): boolean;
|
|
41
38
|
get substream(): SubstreamConfig;
|
|
42
39
|
get wasmPath(): string;
|
|
43
40
|
get zkeyPath(): string;
|
package/dist/context.js
CHANGED
|
@@ -4,7 +4,6 @@ export class Context {
|
|
|
4
4
|
_rpcUrl;
|
|
5
5
|
_connection;
|
|
6
6
|
_programId;
|
|
7
|
-
_stakingProgramId;
|
|
8
7
|
_substream;
|
|
9
8
|
_wasmPath;
|
|
10
9
|
_zkeyPath;
|
|
@@ -12,10 +11,12 @@ export class Context {
|
|
|
12
11
|
constructor(opts = {}) {
|
|
13
12
|
this._rpcUrl = opts.rpcUrl ?? null;
|
|
14
13
|
this._connection = opts.rpcUrl
|
|
15
|
-
? new Connection(opts.rpcUrl,
|
|
14
|
+
? new Connection(opts.rpcUrl, {
|
|
15
|
+
commitment: "confirmed",
|
|
16
|
+
disableRetryOnRateLimit: true,
|
|
17
|
+
})
|
|
16
18
|
: null;
|
|
17
19
|
this._programId = opts.programId ?? null;
|
|
18
|
-
this._stakingProgramId = opts.stakingProgramId ?? null;
|
|
19
20
|
this._substream = opts.substream ?? null;
|
|
20
21
|
this._wasmPath = opts.wasmPath ?? null;
|
|
21
22
|
this._zkeyPath = opts.zkeyPath ?? null;
|
|
@@ -23,6 +24,16 @@ export class Context {
|
|
|
23
24
|
this.wallet =
|
|
24
25
|
w === null ? null : w instanceof Keypair ? new anchor.Wallet(w) : w;
|
|
25
26
|
}
|
|
27
|
+
withRpcUrl(rpcUrl) {
|
|
28
|
+
return new Context({
|
|
29
|
+
rpcUrl,
|
|
30
|
+
programId: this._programId ?? undefined,
|
|
31
|
+
wallet: this.wallet,
|
|
32
|
+
substream: this._substream ?? undefined,
|
|
33
|
+
wasmPath: this._wasmPath ?? undefined,
|
|
34
|
+
zkeyPath: this._zkeyPath ?? undefined,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
26
37
|
get rpcUrl() {
|
|
27
38
|
if (!this._rpcUrl) {
|
|
28
39
|
throw new Error("rpcUrl is required for this operation");
|
|
@@ -41,15 +52,6 @@ export class Context {
|
|
|
41
52
|
}
|
|
42
53
|
return this._programId;
|
|
43
54
|
}
|
|
44
|
-
get stakingProgramId() {
|
|
45
|
-
if (!this._stakingProgramId) {
|
|
46
|
-
throw new Error("stakingProgramId is required for this operation");
|
|
47
|
-
}
|
|
48
|
-
return this._stakingProgramId;
|
|
49
|
-
}
|
|
50
|
-
hasStakingProgramId() {
|
|
51
|
-
return this._stakingProgramId !== null;
|
|
52
|
-
}
|
|
53
55
|
get substream() {
|
|
54
56
|
if (!this._substream) {
|
|
55
57
|
throw new Error("substream config is required for this operation");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
export { Context } from "./context.js";
|
|
2
2
|
export type { SubstreamConfig, SubstreamMode } from "./context.js";
|
|
3
3
|
export * as voidify from "./voidify/index.js";
|
|
4
|
-
export * as staking from "./staking/index.js";
|
|
5
4
|
export { VoidifyProgram } from "./voidify/program.js";
|
|
6
|
-
export { VoidifyStakingProgram } from "./staking/program.js";
|
|
7
5
|
export type { WithdrawArtifact } from "./voidify/withdraw.js";
|
|
8
6
|
export { SubstreamCliClient } from "./substream/client.js";
|
|
9
7
|
export type { SubstreamCliConfig, EventsApiResponse, CursorWire, } from "./substream/client.js";
|
|
10
8
|
export { makeIndexedDBStores } from "./substream/database/indexeddb.js";
|
|
11
9
|
export type { DepositModuleApi, RelayerModuleApi, } from "./substream/modules/index.js";
|
|
12
|
-
export type {
|
|
10
|
+
export type { ChainSyncOptions } from "./substream/chain/index.js";
|
|
11
|
+
export type { DepositRecord, RelayerRecord, EventCursor, SyncProgress, SyncPhase, SyncStatus, SyncStatusReporter, ApplyOutcome, ChainEventRecord, ChainEventWire, EventScope, EventStore, EventProjection, ProjectionStateRecord, ProjectionStateValue, ProjectionStore, SubstreamRepos, SubstreamStores, } from "./substream/types.js";
|
|
13
12
|
export { Note, TOKEN_DECIMALS } from "./utils/note.js";
|
|
14
13
|
export { parseUnits, formatUnits, toBN } from "./utils/amount.js";
|
|
15
14
|
export type { Voidify } from "./idl/voidify/idl.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export { Context } from "./context.js";
|
|
2
2
|
export * as voidify from "./voidify/index.js";
|
|
3
|
-
export * as staking from "./staking/index.js";
|
|
4
3
|
export { VoidifyProgram } from "./voidify/program.js";
|
|
5
|
-
export { VoidifyStakingProgram } from "./staking/program.js";
|
|
6
4
|
export { SubstreamCliClient } from "./substream/client.js";
|
|
7
5
|
export { makeIndexedDBStores } from "./substream/database/indexeddb.js";
|
|
8
6
|
export { Note, TOKEN_DECIMALS } from "./utils/note.js";
|
|
@@ -2,7 +2,6 @@ import express from "express";
|
|
|
2
2
|
import cors from "cors";
|
|
3
3
|
import { updateQuote } from "./switchboard.js";
|
|
4
4
|
import { withdrawIx } from "../../voidify/withdraw.js";
|
|
5
|
-
import { notifyRewardAmountIx } from "../../staking/commands.js";
|
|
6
5
|
import { signAndSend } from "../../utils/tx.js";
|
|
7
6
|
import { relayerLogger as logger } from "../../utils/logger.js";
|
|
8
7
|
export class RelayerHttpServer {
|
|
@@ -58,14 +57,13 @@ export class RelayerHttpServer {
|
|
|
58
57
|
});
|
|
59
58
|
return;
|
|
60
59
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const signature = await signAndSend(this.ctx, ixs);
|
|
60
|
+
const requestCtx = body.rpcUrl
|
|
61
|
+
? this.ctx.withRpcUrl(body.rpcUrl)
|
|
62
|
+
: this.ctx;
|
|
63
|
+
await updateQuote(requestCtx, this.feedId);
|
|
64
|
+
const switchboardQuote = await this.getSwitchboardQuote(requestCtx);
|
|
65
|
+
const ixs = await withdrawIx(requestCtx, new Uint8Array(body.proof), new Uint8Array(body.root), new Uint8Array(body.nullifierHash), body.recipient, requestCtx.publicKey.toBase58(), BigInt(body.fee), BigInt(body.treasury), switchboardQuote, BigInt(body.amount));
|
|
66
|
+
const signature = await signAndSend(requestCtx, ixs);
|
|
69
67
|
logger.info({ signature }, "withdraw submitted");
|
|
70
68
|
res.json({
|
|
71
69
|
success: true,
|
|
@@ -73,10 +71,11 @@ export class RelayerHttpServer {
|
|
|
73
71
|
});
|
|
74
72
|
}
|
|
75
73
|
catch (error) {
|
|
76
|
-
|
|
74
|
+
let error_msg = error instanceof Error ? error.message : String(error);
|
|
75
|
+
logger.error({ error_msg }, "Failed to withdraw");
|
|
77
76
|
res.status(500).json({
|
|
78
77
|
success: false,
|
|
79
|
-
error:
|
|
78
|
+
error: error_msg,
|
|
80
79
|
});
|
|
81
80
|
}
|
|
82
81
|
}
|
|
@@ -97,6 +96,23 @@ export class RelayerHttpServer {
|
|
|
97
96
|
if (!body.recipient || typeof body.recipient !== "string") {
|
|
98
97
|
return "Invalid recipient: must be a string";
|
|
99
98
|
}
|
|
99
|
+
if (body.rpcUrl !== undefined) {
|
|
100
|
+
if (typeof body.rpcUrl !== "string") {
|
|
101
|
+
return "Invalid rpcUrl: must be a string";
|
|
102
|
+
}
|
|
103
|
+
if (body.rpcUrl.length > 2048) {
|
|
104
|
+
return "Invalid rpcUrl: too long";
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const parsed = new URL(body.rpcUrl);
|
|
108
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
109
|
+
return "Invalid rpcUrl: must use http or https";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return "Invalid rpcUrl: not a valid URL";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
100
116
|
for (const field of ["fee", "treasury", "amount"]) {
|
|
101
117
|
const value = body[field];
|
|
102
118
|
if (value === undefined || value === null) {
|
|
@@ -113,9 +129,9 @@ export class RelayerHttpServer {
|
|
|
113
129
|
}
|
|
114
130
|
return null;
|
|
115
131
|
}
|
|
116
|
-
async getSwitchboardQuote() {
|
|
132
|
+
async getSwitchboardQuote(ctx) {
|
|
117
133
|
const sb = await import("@switchboard-xyz/on-demand");
|
|
118
|
-
const queue = await sb.Queue.loadDefault(await sb.AnchorUtils.loadProgramFromConnection(
|
|
134
|
+
const queue = await sb.Queue.loadDefault(await sb.AnchorUtils.loadProgramFromConnection(ctx.connection));
|
|
119
135
|
const [quotePDA] = sb.OracleQuote.getCanonicalPubkey(queue.pubkey, [
|
|
120
136
|
this.feedId,
|
|
121
137
|
]);
|
|
@@ -2,12 +2,19 @@ import * as sb from "@switchboard-xyz/on-demand";
|
|
|
2
2
|
import { CrossbarClient } from "@switchboard-xyz/common";
|
|
3
3
|
import { signAndSend } from "../../utils/tx.js";
|
|
4
4
|
import { VoidifyProgram } from "../../voidify/program.js";
|
|
5
|
+
import { relayerLogger as logger } from "../../utils/logger.js";
|
|
5
6
|
async function getQuoteSlot(ctx, quotePDA) {
|
|
7
|
+
const SWITCHBOARD_QUOTE_ACCOUNT_PAYLOAD_OFFSET = 42;
|
|
8
|
+
const SWITCHBOARD_QUOTE_TAIL_DISCRIMINATOR = "SBOD";
|
|
6
9
|
try {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const account = await ctx.connection.getAccountInfo(quotePDA, "confirmed");
|
|
11
|
+
if (!account)
|
|
12
|
+
return 0;
|
|
13
|
+
const quote = sb.OracleQuote.decode(Buffer.from(account.data).subarray(SWITCHBOARD_QUOTE_ACCOUNT_PAYLOAD_OFFSET));
|
|
14
|
+
if (quote.tailDiscriminator !== SWITCHBOARD_QUOTE_TAIL_DISCRIMINATOR) {
|
|
15
|
+
throw new Error(`Invalid Switchboard quote discriminator: ${quote.tailDiscriminator}`);
|
|
16
|
+
}
|
|
17
|
+
return quote.slot;
|
|
11
18
|
}
|
|
12
19
|
catch (error) {
|
|
13
20
|
throw new Error("Failed to fetch quote slot from chain", {
|
|
@@ -18,13 +25,15 @@ async function getQuoteSlot(ctx, quotePDA) {
|
|
|
18
25
|
export async function updateQuote(ctx, feedID) {
|
|
19
26
|
const voidifyProgram = new VoidifyProgram(ctx.connection, ctx.programId);
|
|
20
27
|
const oracleConfig = await voidifyProgram.program.account.oracleConfig.fetch(voidifyProgram.oracleConfig());
|
|
21
|
-
const maxPriceAgeSlots = (BigInt(oracleConfig.maxPriceAgeSecs.toString()) *
|
|
28
|
+
const maxPriceAgeSlots = (BigInt(oracleConfig.maxPriceAgeSecs.toString()) * 5n) / 2n;
|
|
22
29
|
const queue = await sb.Queue.loadDefault(await sb.AnchorUtils.loadProgramFromConnection(ctx.connection));
|
|
23
30
|
const crossbar = CrossbarClient.default();
|
|
24
31
|
const currentSlot = await ctx.connection.getSlot("confirmed");
|
|
25
32
|
const [quotePDA] = sb.OracleQuote.getCanonicalPubkey(queue.pubkey, [feedID]);
|
|
26
33
|
const quoteSlot = await getQuoteSlot(ctx, quotePDA);
|
|
27
|
-
|
|
34
|
+
const currentAgeSlots = BigInt(currentSlot - quoteSlot);
|
|
35
|
+
if (currentAgeSlots >= maxPriceAgeSlots) {
|
|
36
|
+
logger.info({ currentAgeSlots, maxPriceAgeSlots }, "Updating switchboard oracle");
|
|
28
37
|
const ixs = await queue.fetchManagedUpdateIxs(crossbar, [feedID], {
|
|
29
38
|
variableOverrides: {},
|
|
30
39
|
instructionIdx: 0,
|
package/dist/relayer/types.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { PublicKey } from "@solana/web3.js";
|
|
2
|
-
import type { ChainEventRecord, EventProjection, EventScope, EventStore } from "../../substream/types.js";
|
|
2
|
+
import type { ChainEventRecord, EventProjection, EventScope, EventStore, SyncProgress, SyncStatusReporter } from "../../substream/types.js";
|
|
3
|
+
export interface ChainSyncOptions {
|
|
4
|
+
reporter?: SyncStatusReporter;
|
|
5
|
+
}
|
|
6
|
+
export interface ChainSyncRun {
|
|
7
|
+
updateProgress(progress: SyncProgress): void;
|
|
8
|
+
}
|
|
3
9
|
export interface EventStreamSpec {
|
|
4
10
|
id: string;
|
|
5
11
|
scope: EventScope;
|
|
6
12
|
address: PublicKey;
|
|
7
13
|
getChainLastIndex(): Promise<bigint>;
|
|
8
|
-
|
|
14
|
+
collectBatches(lastSignature?: string, run?: ChainSyncRun): AsyncIterable<ChainEventRecord[]>;
|
|
9
15
|
}
|
|
10
16
|
export declare class ProjectionRegistry {
|
|
11
17
|
private projections;
|
|
@@ -18,7 +24,8 @@ export declare class ChainEventSyncer {
|
|
|
18
24
|
private projections;
|
|
19
25
|
constructor(events: EventStore, projections?: ProjectionRegistry);
|
|
20
26
|
registerProjection(projection: EventProjection): void;
|
|
21
|
-
checkAndSync(spec: EventStreamSpec): Promise<boolean>;
|
|
27
|
+
checkAndSync(spec: EventStreamSpec, run?: ChainSyncRun): Promise<boolean>;
|
|
22
28
|
applyLiveEvent(spec: EventStreamSpec, event: ChainEventRecord): Promise<void>;
|
|
23
29
|
private sync;
|
|
30
|
+
private applyRecords;
|
|
24
31
|
}
|
|
@@ -31,7 +31,7 @@ export class ChainEventSyncer {
|
|
|
31
31
|
registerProjection(projection) {
|
|
32
32
|
this.projections.register(projection);
|
|
33
33
|
}
|
|
34
|
-
async checkAndSync(spec) {
|
|
34
|
+
async checkAndSync(spec, run) {
|
|
35
35
|
const cursor = await this.events.getCursor(spec.scope);
|
|
36
36
|
const localIndex = cursor?.lastIndex ?? -1n;
|
|
37
37
|
let chainLastIndex;
|
|
@@ -40,12 +40,12 @@ export class ChainEventSyncer {
|
|
|
40
40
|
}
|
|
41
41
|
catch (error) {
|
|
42
42
|
substreamLogger.warn({ err: error, streamId: spec.id }, "substream alignment check failed");
|
|
43
|
-
await this.sync(spec);
|
|
43
|
+
await this.sync(spec, run);
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
46
|
if (localIndex >= chainLastIndex)
|
|
47
47
|
return true;
|
|
48
|
-
await this.sync(spec);
|
|
48
|
+
await this.sync(spec, run);
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
async applyLiveEvent(spec, event) {
|
|
@@ -58,11 +58,17 @@ export class ChainEventSyncer {
|
|
|
58
58
|
await this.projections.apply([event]);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
async sync(spec) {
|
|
61
|
+
async sync(spec, run) {
|
|
62
62
|
const cursor = await this.events.getCursor(spec.scope);
|
|
63
|
-
const records
|
|
63
|
+
for await (const records of spec.collectBatches(cursor?.lastSignature ?? undefined, run)) {
|
|
64
|
+
const shouldContinue = await this.applyRecords(spec, records);
|
|
65
|
+
if (!shouldContinue)
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async applyRecords(spec, records) {
|
|
64
70
|
if (records.length === 0)
|
|
65
|
-
return;
|
|
71
|
+
return true;
|
|
66
72
|
const outcome = await this.events.applyBatch(spec.scope, records);
|
|
67
73
|
if (outcome.kind === "gap") {
|
|
68
74
|
substreamLogger.warn({
|
|
@@ -72,8 +78,9 @@ export class ChainEventSyncer {
|
|
|
72
78
|
expected: outcome.expected.toString(),
|
|
73
79
|
got: outcome.got.toString(),
|
|
74
80
|
}, "chain event batch apply found a gap; RPC history window may be exhausted");
|
|
75
|
-
return;
|
|
81
|
+
return false;
|
|
76
82
|
}
|
|
77
83
|
await this.projections.apply(records);
|
|
84
|
+
return true;
|
|
78
85
|
}
|
|
79
86
|
}
|