nebulon-escrow-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/bin/nebulon.js +3 -0
- package/idl/nebulon.json +4320 -0
- package/package.json +33 -0
- package/src/capsules.js +154 -0
- package/src/cli.js +292 -0
- package/src/commands/capsule.js +46 -0
- package/src/commands/config.js +445 -0
- package/src/commands/contract.js +4895 -0
- package/src/commands/hosted.js +90 -0
- package/src/commands/init.js +458 -0
- package/src/commands/invites.js +345 -0
- package/src/commands/login.js +320 -0
- package/src/commands/logout.js +22 -0
- package/src/commands/reset.js +102 -0
- package/src/commands/status.js +72 -0
- package/src/commands/tee.js +169 -0
- package/src/commands/wallet.js +166 -0
- package/src/config.js +80 -0
- package/src/constants.js +70 -0
- package/src/hosted.js +288 -0
- package/src/nebulon.js +121 -0
- package/src/paths.js +24 -0
- package/src/privacy.js +117 -0
- package/src/session.js +131 -0
- package/src/solana.js +196 -0
- package/src/ui.js +36 -0
- package/src/wallets.js +85 -0
package/src/solana.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
const { Connection, PublicKey } = require("@solana/web3.js");
|
|
3
|
+
const { DELEGATION_PROGRAM_ID } = require("@magicblock-labs/ephemeral-rollups-sdk");
|
|
4
|
+
const {
|
|
5
|
+
getAssociatedTokenAddress,
|
|
6
|
+
getAccount,
|
|
7
|
+
getMint,
|
|
8
|
+
} = require("@solana/spl-token");
|
|
9
|
+
|
|
10
|
+
const toSol = (lamports) => lamports / 1_000_000_000;
|
|
11
|
+
|
|
12
|
+
const formatAmount = (value, decimals = 6) =>
|
|
13
|
+
Number(value).toFixed(decimals);
|
|
14
|
+
|
|
15
|
+
const getConnection = (rpcUrl) => new Connection(rpcUrl, "confirmed");
|
|
16
|
+
|
|
17
|
+
const getSolBalance = async (connection, wallet) => {
|
|
18
|
+
const lamports = await connection.getBalance(wallet);
|
|
19
|
+
return toSol(lamports);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getTokenBalance = async (connection, owner, mint) => {
|
|
23
|
+
try {
|
|
24
|
+
const mintInfo = await getMint(connection, mint);
|
|
25
|
+
const ata = await getAssociatedTokenAddress(mint, owner);
|
|
26
|
+
const account = await getAccount(connection, ata);
|
|
27
|
+
const raw = Number(account.amount);
|
|
28
|
+
return raw / 10 ** mintInfo.decimals;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const toPublicKey = (value) => new PublicKey(value);
|
|
35
|
+
|
|
36
|
+
const anchorDiscriminator = (name) =>
|
|
37
|
+
crypto
|
|
38
|
+
.createHash("sha256")
|
|
39
|
+
.update(`account:${name}`)
|
|
40
|
+
.digest()
|
|
41
|
+
.subarray(0, 8);
|
|
42
|
+
|
|
43
|
+
const ESCROW_DISCRIMINATOR = anchorDiscriminator("Escrow");
|
|
44
|
+
const MILESTONE_DISCRIMINATOR = anchorDiscriminator("Milestone");
|
|
45
|
+
|
|
46
|
+
const hasDiscriminator = (data, discriminator) => {
|
|
47
|
+
if (!data || data.length < 8) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return data.subarray(0, 8).equals(discriminator);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const getEscrowMilestoneCounts = async (connection, programId, escrowPda) => {
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const decodeEscrow = (data) => {
|
|
58
|
+
let offset = 8;
|
|
59
|
+
const creator = new PublicKey(data.subarray(offset, offset + 32));
|
|
60
|
+
offset += 32;
|
|
61
|
+
const client = new PublicKey(data.subarray(offset, offset + 32));
|
|
62
|
+
offset += 32;
|
|
63
|
+
const contractor = new PublicKey(data.subarray(offset, offset + 32));
|
|
64
|
+
offset += 32;
|
|
65
|
+
const mint = new PublicKey(data.subarray(offset, offset + 32));
|
|
66
|
+
offset += 32;
|
|
67
|
+
const vaultToken = new PublicKey(data.subarray(offset, offset + 32));
|
|
68
|
+
offset += 32;
|
|
69
|
+
const perVault = new PublicKey(data.subarray(offset, offset + 32));
|
|
70
|
+
offset += 32;
|
|
71
|
+
const escrowId = data.readBigUInt64LE(offset);
|
|
72
|
+
offset += 8;
|
|
73
|
+
const termsHash = data.subarray(offset, offset + 32);
|
|
74
|
+
offset += 32;
|
|
75
|
+
const milestonesHash = data.subarray(offset, offset + 32);
|
|
76
|
+
offset += 32;
|
|
77
|
+
const fundedAmount = data.readBigUInt64LE(offset);
|
|
78
|
+
offset += 8;
|
|
79
|
+
const releasedAmount = data.readBigUInt64LE(offset);
|
|
80
|
+
offset += 8;
|
|
81
|
+
const fundedAt = Number(data.readBigInt64LE(offset));
|
|
82
|
+
offset += 8;
|
|
83
|
+
const disputeOpen = data.readUInt8(offset) !== 0;
|
|
84
|
+
offset += 1;
|
|
85
|
+
const paidOut = data.readUInt8(offset) !== 0;
|
|
86
|
+
offset += 1;
|
|
87
|
+
const fundingOk = data.readUInt8(offset) !== 0;
|
|
88
|
+
offset += 1;
|
|
89
|
+
const readyToClaim = data.readUInt8(offset) !== 0;
|
|
90
|
+
offset += 1;
|
|
91
|
+
const timeoutRefundReady = data.readUInt8(offset) !== 0;
|
|
92
|
+
offset += 1;
|
|
93
|
+
const timeoutFundsReady = data.readUInt8(offset) !== 0;
|
|
94
|
+
offset += 1;
|
|
95
|
+
const judge = new PublicKey(data.subarray(offset, offset + 32));
|
|
96
|
+
offset += 32;
|
|
97
|
+
const bump = data.readUInt8(offset);
|
|
98
|
+
offset += 1;
|
|
99
|
+
const perVaultBump = data.readUInt8(offset);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
creator,
|
|
103
|
+
client,
|
|
104
|
+
contractor,
|
|
105
|
+
mint,
|
|
106
|
+
vaultToken,
|
|
107
|
+
perVault,
|
|
108
|
+
escrowId,
|
|
109
|
+
termsHash,
|
|
110
|
+
milestonesHash,
|
|
111
|
+
fundedAmount,
|
|
112
|
+
releasedAmount,
|
|
113
|
+
fundedAt,
|
|
114
|
+
disputeOpen,
|
|
115
|
+
paidOut,
|
|
116
|
+
fundingOk,
|
|
117
|
+
readyToClaim,
|
|
118
|
+
timeoutRefundReady,
|
|
119
|
+
timeoutFundsReady,
|
|
120
|
+
judge,
|
|
121
|
+
bump,
|
|
122
|
+
perVaultBump,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const getEscrowState = async (connection, programId, escrowPda) => {
|
|
127
|
+
if (!escrowPda) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const escrowKey = toPublicKey(escrowPda);
|
|
131
|
+
const info = await connection.getAccountInfo(escrowKey, "confirmed");
|
|
132
|
+
if (!info) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const isDelegated = info.owner.equals(DELEGATION_PROGRAM_ID);
|
|
136
|
+
if (
|
|
137
|
+
!info.owner.equals(programId) &&
|
|
138
|
+
!isDelegated
|
|
139
|
+
) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if (!hasDiscriminator(info.data, ESCROW_DISCRIMINATOR)) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (info.data.length < 336) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const decoded = decodeEscrow(info.data);
|
|
149
|
+
decoded._ownerIsDelegated = isDelegated;
|
|
150
|
+
return decoded;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const decodeMilestone = (data) => {
|
|
154
|
+
let offset = 8;
|
|
155
|
+
const escrow = new PublicKey(data.subarray(offset, offset + 32));
|
|
156
|
+
offset += 32;
|
|
157
|
+
const index = data.readUInt8(offset);
|
|
158
|
+
offset += 1;
|
|
159
|
+
const status = data.readUInt8(offset);
|
|
160
|
+
offset += 1;
|
|
161
|
+
const submittedAt = data.readBigInt64LE(offset);
|
|
162
|
+
offset += 8;
|
|
163
|
+
const creator =
|
|
164
|
+
data.length >= offset + 32
|
|
165
|
+
? new PublicKey(data.subarray(offset, offset + 32))
|
|
166
|
+
: null;
|
|
167
|
+
return { escrow, index, status, submittedAt, creator };
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const getMilestoneState = async (connection, programId, milestonePda) => {
|
|
171
|
+
if (!milestonePda) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const info = await connection.getAccountInfo(milestonePda, "confirmed");
|
|
175
|
+
if (!info) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
if (!info.owner.equals(programId)) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
if (!hasDiscriminator(info.data, MILESTONE_DISCRIMINATOR)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return decodeMilestone(info.data);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
formatAmount,
|
|
189
|
+
getConnection,
|
|
190
|
+
getSolBalance,
|
|
191
|
+
getTokenBalance,
|
|
192
|
+
toPublicKey,
|
|
193
|
+
getEscrowMilestoneCounts,
|
|
194
|
+
getEscrowState,
|
|
195
|
+
getMilestoneState,
|
|
196
|
+
};
|
package/src/ui.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
|
|
3
|
+
const banner = () => {
|
|
4
|
+
const lines = [
|
|
5
|
+
"███╗ ██╗███████╗██████╗ ██╗ ██╗██╗ ██████╗ ███╗ ██╗",
|
|
6
|
+
"████╗ ██║██╔════╝██╔══██╗██║ ██║██║ ██╔═══██╗████╗ ██║",
|
|
7
|
+
"██╔██╗ ██║█████╗ ██████╔╝██║ ██║██║ ██║ ██║██╔██╗ ██║",
|
|
8
|
+
"██║╚██╗██║██╔══╝ ██╔══██╗██║ ██║██║ ██║ ██║██║╚██╗██║",
|
|
9
|
+
"██║ ╚████║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝██║ ╚████║",
|
|
10
|
+
"╚═╝ ╚═══╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝",
|
|
11
|
+
"",
|
|
12
|
+
"Nebulon - Private, milestone-based escrow protocol",
|
|
13
|
+
];
|
|
14
|
+
console.log(chalk.cyan(lines.join("\n")));
|
|
15
|
+
console.log("");
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const keyValue = (label, value) => {
|
|
19
|
+
const pad = label.padEnd(24, " ");
|
|
20
|
+
console.log(`${pad} ${value}`);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const successMessage = (message = "Action completed successfully.") => {
|
|
24
|
+
console.log(chalk.green(`√ ${message}`));
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const errorMessage = (message = "Action failed.") => {
|
|
28
|
+
console.error(chalk.red(`✖ ${message}`));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
banner,
|
|
33
|
+
keyValue,
|
|
34
|
+
successMessage,
|
|
35
|
+
errorMessage,
|
|
36
|
+
};
|
package/src/wallets.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { Keypair } = require("@solana/web3.js");
|
|
4
|
+
const { capsuleWalletsDir, capsuleRoot } = require("./paths");
|
|
5
|
+
const { getActiveCapsule } = require("./capsules");
|
|
6
|
+
const { resolveWalletPath } = require("./config");
|
|
7
|
+
|
|
8
|
+
const validateKeypairJson = (raw) => {
|
|
9
|
+
if (!Array.isArray(raw) || raw.length < 32) {
|
|
10
|
+
throw new Error("Invalid keypair JSON.");
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const writeKeypairFile = (targetPath, secretKey) => {
|
|
15
|
+
fs.writeFileSync(targetPath, JSON.stringify(Array.from(secretKey)));
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const generateWallet = (name) => {
|
|
19
|
+
const keypair = Keypair.generate();
|
|
20
|
+
const capsule = getActiveCapsule();
|
|
21
|
+
const targetPath = path.join(capsuleWalletsDir(capsule), `${name}.json`);
|
|
22
|
+
writeKeypairFile(targetPath, keypair.secretKey);
|
|
23
|
+
return { keypair, path: targetPath };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const importWalletFromFile = (name, sourcePath) => {
|
|
27
|
+
if (!fs.existsSync(sourcePath)) {
|
|
28
|
+
throw new Error(`Wallet file not found: ${sourcePath}`);
|
|
29
|
+
}
|
|
30
|
+
const raw = JSON.parse(fs.readFileSync(sourcePath, "utf8"));
|
|
31
|
+
validateKeypairJson(raw);
|
|
32
|
+
const capsule = getActiveCapsule();
|
|
33
|
+
const targetPath = path.join(capsuleWalletsDir(capsule), `${name}.json`);
|
|
34
|
+
fs.writeFileSync(targetPath, JSON.stringify(raw));
|
|
35
|
+
const keypair = Keypair.fromSecretKey(Uint8Array.from(raw));
|
|
36
|
+
return { keypair, path: targetPath };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const listWallets = (config) => {
|
|
40
|
+
return Object.entries(config.wallets || {}).map(([name, entry]) => ({
|
|
41
|
+
name,
|
|
42
|
+
entry,
|
|
43
|
+
path: resolveWalletPath(entry),
|
|
44
|
+
active: config.activeWallet === name,
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const setActiveWallet = (config, name) => {
|
|
49
|
+
if (!config.wallets || !config.wallets[name]) {
|
|
50
|
+
throw new Error(`Wallet not found: ${name}`);
|
|
51
|
+
}
|
|
52
|
+
config.activeWallet = name;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const addWalletEntry = (config, name, walletPath) => {
|
|
56
|
+
const capsule = getActiveCapsule();
|
|
57
|
+
const baseDir = capsuleRoot(capsule);
|
|
58
|
+
const relativePath = path.isAbsolute(walletPath)
|
|
59
|
+
? path.relative(baseDir, walletPath)
|
|
60
|
+
: walletPath;
|
|
61
|
+
config.wallets[name] = { path: relativePath };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const loadWalletKeypair = (config, name) => {
|
|
65
|
+
const entry = config.wallets && config.wallets[name];
|
|
66
|
+
if (!entry) {
|
|
67
|
+
throw new Error(`Wallet not found: ${name}`);
|
|
68
|
+
}
|
|
69
|
+
const resolved = resolveWalletPath(entry);
|
|
70
|
+
if (!resolved || !fs.existsSync(resolved)) {
|
|
71
|
+
throw new Error(`Wallet file missing: ${resolved || entry.path}`);
|
|
72
|
+
}
|
|
73
|
+
const raw = JSON.parse(fs.readFileSync(resolved, "utf8"));
|
|
74
|
+
validateKeypairJson(raw);
|
|
75
|
+
return Keypair.fromSecretKey(Uint8Array.from(raw));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
generateWallet,
|
|
80
|
+
importWalletFromFile,
|
|
81
|
+
listWallets,
|
|
82
|
+
setActiveWallet,
|
|
83
|
+
addWalletEntry,
|
|
84
|
+
loadWalletKeypair,
|
|
85
|
+
};
|