@toon-protocol/rig 3.6.0 → 4.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 +112 -153
- package/dist/{chunk-HJDPM37T.js → chunk-7VCVR4GK.js} +12 -8
- package/dist/{chunk-HJDPM37T.js.map → chunk-7VCVR4GK.js.map} +1 -1
- package/dist/chunk-CDSFVZDV.js +290 -0
- package/dist/chunk-CDSFVZDV.js.map +1 -0
- package/dist/{chunk-B5ISARMU.js → chunk-GVXBUL3C.js} +15 -2
- package/dist/chunk-GVXBUL3C.js.map +1 -0
- package/dist/chunk-LTFMPOHU.js +194 -0
- package/dist/chunk-LTFMPOHU.js.map +1 -0
- package/dist/{chunk-2KJJ7LSO.js → chunk-NCEMQUPA.js} +40 -2
- package/dist/chunk-NCEMQUPA.js.map +1 -0
- package/dist/{chunk-LCYCKWAT.js → chunk-YZRR2UO6.js} +86 -5
- package/dist/chunk-YZRR2UO6.js.map +1 -0
- package/dist/cli/rig.js +631 -568
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +10 -115
- package/dist/index.js +16 -122
- package/dist/index.js.map +1 -1
- package/dist/{publisher-BFcnZeJI.d.ts → publisher-DMJ06cuL.d.ts} +98 -2
- package/dist/standalone/index.d.ts +241 -438
- package/dist/standalone/index.js +19 -11
- package/dist/standalone-mode-KTYRTUS6.js +390 -0
- package/dist/standalone-mode-KTYRTUS6.js.map +1 -0
- package/package.json +7 -5
- package/dist/chunk-2KJJ7LSO.js.map +0 -1
- package/dist/chunk-B5ISARMU.js.map +0 -1
- package/dist/chunk-IZ6KLYQ6.js +0 -724
- package/dist/chunk-IZ6KLYQ6.js.map +0 -1
- package/dist/chunk-LCYCKWAT.js.map +0 -1
- package/dist/chunk-PWNZWQOH.js +0 -404
- package/dist/chunk-PWNZWQOH.js.map +0 -1
- package/dist/chunk-UZZ2RN7A.js +0 -469
- package/dist/chunk-UZZ2RN7A.js.map +0 -1
- package/dist/network-bootstrap-27FYE3N7.js +0 -40
- package/dist/network-bootstrap-27FYE3N7.js.map +0 -1
- package/dist/standalone-mode-5MNNJC2D.js +0 -928
- package/dist/standalone-mode-5MNNJC2D.js.map +0 -1
package/dist/standalone/index.js
CHANGED
|
@@ -1,42 +1,50 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
deriveRouteDestinations,
|
|
2
|
+
ConnectorPublishError,
|
|
3
|
+
ConnectorPublisher,
|
|
5
4
|
extractArweaveTxId
|
|
6
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-CDSFVZDV.js";
|
|
7
6
|
import {
|
|
8
7
|
ChannelMapCorruptError,
|
|
9
8
|
ChannelMapStore,
|
|
10
9
|
DEFAULT_DAEMON_PORT,
|
|
11
10
|
DaemonIdentityConflictError,
|
|
11
|
+
MAX_ACCOUNT_INDEX,
|
|
12
12
|
NonceLock,
|
|
13
13
|
RIG_CHANNEL_MAP_FILENAME,
|
|
14
14
|
StandaloneLockError,
|
|
15
15
|
channelStatus,
|
|
16
16
|
checkDaemonIdentity,
|
|
17
|
+
counterpartyMatch,
|
|
17
18
|
defaultDaemonPort,
|
|
18
19
|
defaultLockDir,
|
|
20
|
+
deriveNostrKeyFromMnemonic,
|
|
21
|
+
nostrDerivationPath,
|
|
19
22
|
recordKey,
|
|
20
|
-
resolveChannelPaths
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
resolveChannelPaths,
|
|
24
|
+
sameSettlementAddress
|
|
25
|
+
} from "../chunk-YZRR2UO6.js";
|
|
26
|
+
import "../chunk-GVXBUL3C.js";
|
|
23
27
|
export {
|
|
24
28
|
ChannelMapCorruptError,
|
|
25
29
|
ChannelMapStore,
|
|
30
|
+
ConnectorPublishError,
|
|
31
|
+
ConnectorPublisher,
|
|
26
32
|
DEFAULT_DAEMON_PORT,
|
|
27
33
|
DaemonIdentityConflictError,
|
|
34
|
+
MAX_ACCOUNT_INDEX,
|
|
28
35
|
NonceLock,
|
|
29
36
|
RIG_CHANNEL_MAP_FILENAME,
|
|
30
37
|
StandaloneLockError,
|
|
31
|
-
StandalonePublishError,
|
|
32
|
-
StandalonePublisher,
|
|
33
38
|
channelStatus,
|
|
34
39
|
checkDaemonIdentity,
|
|
40
|
+
counterpartyMatch,
|
|
35
41
|
defaultDaemonPort,
|
|
36
42
|
defaultLockDir,
|
|
37
|
-
|
|
43
|
+
deriveNostrKeyFromMnemonic,
|
|
38
44
|
extractArweaveTxId,
|
|
45
|
+
nostrDerivationPath,
|
|
39
46
|
recordKey,
|
|
40
|
-
resolveChannelPaths
|
|
47
|
+
resolveChannelPaths,
|
|
48
|
+
sameSettlementAddress
|
|
41
49
|
};
|
|
42
50
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveIdentity
|
|
3
|
+
} from "./chunk-LTFMPOHU.js";
|
|
4
|
+
import {
|
|
5
|
+
fetchRemoteState
|
|
6
|
+
} from "./chunk-NCEMQUPA.js";
|
|
7
|
+
import {
|
|
8
|
+
ConnectorPublisher
|
|
9
|
+
} from "./chunk-CDSFVZDV.js";
|
|
10
|
+
import {
|
|
11
|
+
ChannelMapStore,
|
|
12
|
+
NonceLock,
|
|
13
|
+
RIG_CHANNEL_MAP_FILENAME,
|
|
14
|
+
checkDaemonIdentity,
|
|
15
|
+
deriveNostrKeyFromMnemonic,
|
|
16
|
+
standaloneForced
|
|
17
|
+
} from "./chunk-YZRR2UO6.js";
|
|
18
|
+
import "./chunk-GVXBUL3C.js";
|
|
19
|
+
|
|
20
|
+
// src/cli/standalone-mode.ts
|
|
21
|
+
import { readFileSync } from "fs";
|
|
22
|
+
import { homedir } from "os";
|
|
23
|
+
import { join } from "path";
|
|
24
|
+
import {
|
|
25
|
+
ToonClient,
|
|
26
|
+
defaultDestinationFor
|
|
27
|
+
} from "@toon-protocol/client";
|
|
28
|
+
function configDir(env) {
|
|
29
|
+
return env["TOON_CLIENT_HOME"] ?? join(homedir(), ".toon-client");
|
|
30
|
+
}
|
|
31
|
+
function readClientConfig(path) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err.code === "ENOENT") return {};
|
|
36
|
+
throw new Error(
|
|
37
|
+
`failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
var MissingUplinkError = class extends Error {
|
|
42
|
+
constructor(configPath) {
|
|
43
|
+
super(
|
|
44
|
+
`no connector configured: set TOON_CONNECTOR to the node's URL (the one whose GET /ilp describes it), run \`rig entry <url>\`, or add connectorUrl to ${configPath}`
|
|
45
|
+
);
|
|
46
|
+
this.name = "MissingUplinkError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function chainFamilyOf(chain) {
|
|
50
|
+
if (!chain) return void 0;
|
|
51
|
+
const family = chain.split(":")[0];
|
|
52
|
+
return family === "evm" || family === "solana" ? family : void 0;
|
|
53
|
+
}
|
|
54
|
+
function resolveConnectorSettings(args) {
|
|
55
|
+
const { env, file } = args;
|
|
56
|
+
const warnings = [];
|
|
57
|
+
let connectorUrl;
|
|
58
|
+
let connectorSource = null;
|
|
59
|
+
if (env["TOON_CONNECTOR"]) {
|
|
60
|
+
connectorUrl = env["TOON_CONNECTOR"];
|
|
61
|
+
connectorSource = "env";
|
|
62
|
+
} else if (env["TOON_CLIENT_PROXY_URL"]) {
|
|
63
|
+
connectorUrl = env["TOON_CLIENT_PROXY_URL"];
|
|
64
|
+
connectorSource = "legacy-env";
|
|
65
|
+
warnings.push(
|
|
66
|
+
"rig: TOON_CLIENT_PROXY_URL is the pre-2.0 spelling \u2014 set TOON_CONNECTOR to the node URL instead"
|
|
67
|
+
);
|
|
68
|
+
} else if (file.connectorUrl) {
|
|
69
|
+
connectorUrl = file.connectorUrl;
|
|
70
|
+
connectorSource = "config";
|
|
71
|
+
} else if (file.proxyUrl) {
|
|
72
|
+
connectorUrl = file.proxyUrl;
|
|
73
|
+
connectorSource = "legacy-config";
|
|
74
|
+
warnings.push(
|
|
75
|
+
"rig: config `proxyUrl` is the pre-2.0 spelling \u2014 `rig entry <url>` records `connectorUrl`"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (env["TOON_CLIENT_BTP_URL"] || file.btpUrl) {
|
|
79
|
+
warnings.push(
|
|
80
|
+
"rig: btpUrl is ignored since 4.0 \u2014 the client reads the BTP endpoint from the node's GET /ilp"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const chainKey = env["TOON_CLIENT_CHAIN"] ?? file.chain ?? file.supportedChains?.[0];
|
|
84
|
+
const chain = chainFamilyOf(chainKey);
|
|
85
|
+
if (chainKey && !chain) {
|
|
86
|
+
warnings.push(
|
|
87
|
+
`rig: chain ${JSON.stringify(chainKey)} is not a settlement chain the connector supports (evm | solana) \u2014 ignoring it`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const rpcUrl = env["TOON_CLIENT_RPC_URL"] ?? file.rpcUrl ?? (chainKey ? file.chainRpcUrls?.[chainKey] : void 0) ?? (chain ? file.chainRpcUrls?.[chain] : void 0);
|
|
91
|
+
const transportRaw = env["TOON_CLIENT_TRANSPORT"] ?? file.transport;
|
|
92
|
+
const transport = transportRaw === "http" || transportRaw === "btp" ? transportRaw : "auto";
|
|
93
|
+
const derivationRaw = env["TOON_CLIENT_KEY_DERIVATION"] ?? file.keyDerivation;
|
|
94
|
+
const keyDerivation = derivationRaw === "standard" ? "standard" : "legacy";
|
|
95
|
+
const depositRaw = env["TOON_CLIENT_DEPOSIT"] ?? file.deposit;
|
|
96
|
+
const publishDestination = env["TOON_CLIENT_PUBLISH_DESTINATION"] ?? env["TOON_CLIENT_DESTINATION"] ?? file.publishDestination ?? file.destination;
|
|
97
|
+
const storeDestination = args.storeDestinationOverride ?? env["TOON_CLIENT_STORE_DESTINATION"] ?? file.storeDestination;
|
|
98
|
+
const relayUrl = env["TOON_CLIENT_RELAY_URL"] ?? file.relayUrl;
|
|
99
|
+
return {
|
|
100
|
+
...connectorUrl ? { connectorUrl } : {},
|
|
101
|
+
connectorSource,
|
|
102
|
+
...publishDestination ? { publishDestination } : {},
|
|
103
|
+
...storeDestination ? { storeDestination } : {},
|
|
104
|
+
...env["TOON_CLIENT_STORE_CONNECTOR_URL"] ?? file.storeConnectorUrl ? {
|
|
105
|
+
storeConnectorUrl: env["TOON_CLIENT_STORE_CONNECTOR_URL"] ?? file.storeConnectorUrl
|
|
106
|
+
} : {},
|
|
107
|
+
...env["TOON_CLIENT_STORE_SEAL_TO"] ?? file.storeSealTo ? { storeSealTo: env["TOON_CLIENT_STORE_SEAL_TO"] ?? file.storeSealTo } : {},
|
|
108
|
+
...relayUrl ? { relayUrl } : {},
|
|
109
|
+
...chain ? { chain } : {},
|
|
110
|
+
...chainKey ? { chainKey } : {},
|
|
111
|
+
...rpcUrl ? { rpcUrl } : {},
|
|
112
|
+
transport,
|
|
113
|
+
keyDerivation,
|
|
114
|
+
channelStorePath: env["TOON_CLIENT_CHANNEL_STORE"] ?? file.channelStorePath ?? join(args.configDir, "channels.json"),
|
|
115
|
+
...depositRaw !== void 0 ? { deposit: BigInt(depositRaw) } : {},
|
|
116
|
+
eventFee: BigInt(
|
|
117
|
+
env["TOON_CLIENT_FEE_PER_EVENT"] ?? file.feePerEvent ?? "0"
|
|
118
|
+
),
|
|
119
|
+
...env["RIG_SOLANA_KEY_FILE"] ?? file.solanaKeyFile ? { solanaKeyFile: env["RIG_SOLANA_KEY_FILE"] ?? file.solanaKeyFile } : {},
|
|
120
|
+
...env["RIG_EVM_PRIVATE_KEY"] ?? file.evmPrivateKey ? { evmPrivateKey: env["RIG_EVM_PRIVATE_KEY"] ?? file.evmPrivateKey } : {},
|
|
121
|
+
...file.uploadTimeoutMs !== void 0 ? { uploadTimeoutMs: file.uploadTimeoutMs } : {},
|
|
122
|
+
warnings
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function defaultStoreDestinationFor(desc) {
|
|
126
|
+
const prefixes = desc.routes.map((r) => r.prefix);
|
|
127
|
+
return prefixes.find((p) => p === "store" || p.endsWith(".store")) ?? prefixes.find((p) => p === "ario" || p.endsWith(".ario")) ?? prefixes.find((p) => /store/i.test(p));
|
|
128
|
+
}
|
|
129
|
+
function resolveDestinations(desc, settings, connectorUrl) {
|
|
130
|
+
const publish = settings.publishDestination ?? defaultDestinationFor(desc);
|
|
131
|
+
if (!publish) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`the connector at ${connectorUrl} publishes no priced address of its own \u2014 set publishDestination / TOON_CLIENT_PUBLISH_DESTINATION to one of its GET /ilp routes`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const store = settings.storeDestination ?? defaultStoreDestinationFor(desc);
|
|
137
|
+
if (!store) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`the connector at ${connectorUrl} prices no store route (none named *.store or *.ario) \u2014 set storeDestination / TOON_CLIENT_STORE_DESTINATION to the route its store answers on`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return { publish, store };
|
|
143
|
+
}
|
|
144
|
+
function readSolanaKeyFile(path) {
|
|
145
|
+
let parsed;
|
|
146
|
+
try {
|
|
147
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
148
|
+
} catch (err) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`failed to read the Solana key file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (!Array.isArray(parsed) || parsed.length !== 64 && parsed.length !== 32 || !parsed.every((n) => Number.isInteger(n) && n >= 0 && n <= 255)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`${path} is not a Solana keypair file (expected a JSON array of 64 bytes)`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return Uint8Array.from(parsed);
|
|
159
|
+
}
|
|
160
|
+
function recordFor(state, args) {
|
|
161
|
+
const tokenNetwork = state.domain.tokenNetwork ?? state.domain.programId ?? "";
|
|
162
|
+
return {
|
|
163
|
+
channelId: state.channelId,
|
|
164
|
+
peerId: args.peerId,
|
|
165
|
+
identity: args.identity,
|
|
166
|
+
destination: args.destination,
|
|
167
|
+
chain: state.domain.chain,
|
|
168
|
+
tokenNetwork,
|
|
169
|
+
context: {
|
|
170
|
+
chainType: state.chain,
|
|
171
|
+
chainId: state.domain.chainId ?? 0,
|
|
172
|
+
tokenNetworkAddress: tokenNetwork,
|
|
173
|
+
tokenAddress: state.domain.token,
|
|
174
|
+
recipient: state.domain.counterparty
|
|
175
|
+
},
|
|
176
|
+
depositTotal: state.depositTotal.toString()
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function buildMoneyOps(args) {
|
|
180
|
+
const { client, channelMap } = args;
|
|
181
|
+
const known = (channelId) => channelMap.list().some((r) => r.channelId === channelId);
|
|
182
|
+
const requireSameChannel = async (record) => {
|
|
183
|
+
const state = await client.channel.state();
|
|
184
|
+
if (state.channelId !== record.channelId) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`this identity's channel with ${args.destination} is ${state.channelId}, not the recorded ${record.channelId} \u2014 the record is stale or belongs to another node`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
async openChannel(opts) {
|
|
192
|
+
const before = await client.channel.state().catch(() => void 0);
|
|
193
|
+
const resumed = before !== void 0 && known(before.channelId);
|
|
194
|
+
const state = opts?.deposit !== void 0 && before?.status === "open" ? await client.channel.deposit(opts.deposit) : await client.channel.open(
|
|
195
|
+
opts?.deposit !== void 0 ? { deposit: opts.deposit } : {}
|
|
196
|
+
);
|
|
197
|
+
channelMap.record(recordFor(state, args));
|
|
198
|
+
return {
|
|
199
|
+
channelId: state.channelId,
|
|
200
|
+
resumed,
|
|
201
|
+
destination: args.destination,
|
|
202
|
+
chain: state.domain.chain,
|
|
203
|
+
peerId: args.peerId,
|
|
204
|
+
depositTotal: state.depositTotal.toString(),
|
|
205
|
+
...opts?.deposit !== void 0 ? { depositAdded: opts.deposit.toString() } : {}
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
async closeChannel(record) {
|
|
209
|
+
await requireSameChannel(record);
|
|
210
|
+
const result = await client.channel.close();
|
|
211
|
+
const closedAt = result.closedAt ?? BigInt(Math.floor(Date.now() / 1e3));
|
|
212
|
+
return {
|
|
213
|
+
channelId: record.channelId,
|
|
214
|
+
...result.txHash ? { txHash: result.txHash } : {},
|
|
215
|
+
closedAt: closedAt.toString(),
|
|
216
|
+
settleableAt: (result.settleableAt ?? closedAt).toString()
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
async settleChannel(record) {
|
|
220
|
+
await requireSameChannel(record);
|
|
221
|
+
const result = await client.channel.settle();
|
|
222
|
+
channelMap.supersede(record);
|
|
223
|
+
return {
|
|
224
|
+
channelId: record.channelId,
|
|
225
|
+
...result.txHash ? { txHash: result.txHash } : {}
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
async walletChainBalances() {
|
|
229
|
+
return (await client.wallet.balances()).map((b) => ({
|
|
230
|
+
chain: b.chain,
|
|
231
|
+
chainKey: b.chainKey,
|
|
232
|
+
address: b.address,
|
|
233
|
+
...b.native ? { native: b.native } : {},
|
|
234
|
+
tokens: b.tokens,
|
|
235
|
+
...b.unreadable !== void 0 ? { unreadable: b.unreadable } : {},
|
|
236
|
+
...b.error !== void 0 ? { error: b.error } : {}
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
var UnconfiguredPublisher = class {
|
|
242
|
+
constructor(error) {
|
|
243
|
+
this.error = error;
|
|
244
|
+
}
|
|
245
|
+
error;
|
|
246
|
+
getFeeRates() {
|
|
247
|
+
return Promise.reject(this.error);
|
|
248
|
+
}
|
|
249
|
+
uploadGitObject() {
|
|
250
|
+
return Promise.reject(this.error);
|
|
251
|
+
}
|
|
252
|
+
publishEvent() {
|
|
253
|
+
return Promise.reject(this.error);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
async function createStandaloneContext(options) {
|
|
257
|
+
const { env } = options;
|
|
258
|
+
const warn = (line) => options.warn(line);
|
|
259
|
+
const dir = configDir(env);
|
|
260
|
+
const configPath = join(dir, "config.json");
|
|
261
|
+
const file = readClientConfig(configPath);
|
|
262
|
+
const identity = await resolveIdentity(options);
|
|
263
|
+
const nostr = deriveNostrKeyFromMnemonic(
|
|
264
|
+
identity.mnemonic,
|
|
265
|
+
identity.accountIndex
|
|
266
|
+
);
|
|
267
|
+
const settings = resolveConnectorSettings({
|
|
268
|
+
env,
|
|
269
|
+
file,
|
|
270
|
+
configDir: dir,
|
|
271
|
+
...options.storeDestination ? { storeDestinationOverride: options.storeDestination } : {}
|
|
272
|
+
});
|
|
273
|
+
for (const line of settings.warnings) warn(line);
|
|
274
|
+
const defaultRelayUrls = settings.relayUrl ? [settings.relayUrl] : [];
|
|
275
|
+
if (!settings.connectorUrl) {
|
|
276
|
+
const missing = new MissingUplinkError(configPath);
|
|
277
|
+
if (options.requireUplink === false) {
|
|
278
|
+
return {
|
|
279
|
+
ownerPubkey: nostr.pubkey,
|
|
280
|
+
identitySource: identity.source,
|
|
281
|
+
identitySourceLabel: identity.sourceLabel,
|
|
282
|
+
publisher: new UnconfiguredPublisher(missing),
|
|
283
|
+
defaultRelayUrls,
|
|
284
|
+
fetchRemote: (args) => fetchRemoteState(args),
|
|
285
|
+
stop: () => Promise.resolve()
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
throw missing;
|
|
289
|
+
}
|
|
290
|
+
const connectorUrl = settings.connectorUrl;
|
|
291
|
+
if (!standaloneForced(env)) await checkDaemonIdentity(nostr.pubkey);
|
|
292
|
+
const lock = await NonceLock.acquire(nostr.pubkey);
|
|
293
|
+
const clients = [];
|
|
294
|
+
const stop = async () => {
|
|
295
|
+
for (const c of clients) await c.close();
|
|
296
|
+
lock.release();
|
|
297
|
+
};
|
|
298
|
+
try {
|
|
299
|
+
const payerKeys = {
|
|
300
|
+
mnemonic: identity.mnemonic,
|
|
301
|
+
accountIndex: identity.accountIndex,
|
|
302
|
+
keyDerivation: settings.keyDerivation,
|
|
303
|
+
...settings.solanaKeyFile ? { solanaSecretKey: readSolanaKeyFile(settings.solanaKeyFile) } : {},
|
|
304
|
+
...settings.evmPrivateKey ? { evmPrivateKey: settings.evmPrivateKey } : {}
|
|
305
|
+
};
|
|
306
|
+
const shared = {
|
|
307
|
+
...payerKeys,
|
|
308
|
+
...settings.chain ? { chain: settings.chain } : {},
|
|
309
|
+
...settings.rpcUrl ? { rpcUrl: settings.rpcUrl } : {},
|
|
310
|
+
transport: settings.transport,
|
|
311
|
+
autoOpenChannel: true,
|
|
312
|
+
...settings.deposit !== void 0 ? { deposit: settings.deposit } : {}
|
|
313
|
+
};
|
|
314
|
+
if (settings.chain && !settings.rpcUrl) {
|
|
315
|
+
warn(
|
|
316
|
+
`rig: no rpcUrl configured for ${settings.chain} \u2014 the client falls back to its devnet preset; set TOON_CLIENT_RPC_URL (or config rpcUrl) for a mainnet node`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
const client = await ToonClient.create({
|
|
320
|
+
...shared,
|
|
321
|
+
connector: connectorUrl,
|
|
322
|
+
channelStore: settings.channelStorePath
|
|
323
|
+
});
|
|
324
|
+
clients.push(client);
|
|
325
|
+
const desc = await client.describe();
|
|
326
|
+
const destinations = resolveDestinations(desc, settings, connectorUrl);
|
|
327
|
+
let storeClient = client;
|
|
328
|
+
if (settings.storeConnectorUrl && normalizeUrl(settings.storeConnectorUrl) !== normalizeUrl(connectorUrl)) {
|
|
329
|
+
storeClient = await ToonClient.create({
|
|
330
|
+
...shared,
|
|
331
|
+
connector: settings.storeConnectorUrl,
|
|
332
|
+
channelStore: settings.channelStorePath.replace(
|
|
333
|
+
/(\.json)?$/,
|
|
334
|
+
".store.json"
|
|
335
|
+
)
|
|
336
|
+
});
|
|
337
|
+
clients.push(storeClient);
|
|
338
|
+
}
|
|
339
|
+
const publisher = new ConnectorPublisher({
|
|
340
|
+
publish: { client, destination: destinations.publish },
|
|
341
|
+
store: {
|
|
342
|
+
client: storeClient,
|
|
343
|
+
destination: destinations.store,
|
|
344
|
+
...storeClient === client && settings.storeSealTo ? { sealTo: settings.storeSealTo } : {}
|
|
345
|
+
},
|
|
346
|
+
nostrSecretKey: nostr.secretKey,
|
|
347
|
+
eventFee: settings.eventFee,
|
|
348
|
+
...settings.uploadTimeoutMs !== void 0 ? { uploadTimeoutMs: settings.uploadTimeoutMs } : {},
|
|
349
|
+
warn
|
|
350
|
+
});
|
|
351
|
+
const channelMap = new ChannelMapStore({
|
|
352
|
+
mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),
|
|
353
|
+
watermarkPath: settings.channelStorePath
|
|
354
|
+
});
|
|
355
|
+
const money = buildMoneyOps({
|
|
356
|
+
client,
|
|
357
|
+
channelMap,
|
|
358
|
+
identity: nostr.pubkey,
|
|
359
|
+
destination: options.channelDestination ?? destinations.publish,
|
|
360
|
+
peerId: desc.edgeIdentity?.keyId ?? connectorUrl
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
ownerPubkey: nostr.pubkey,
|
|
364
|
+
identitySource: identity.source,
|
|
365
|
+
identitySourceLabel: identity.sourceLabel,
|
|
366
|
+
publisher,
|
|
367
|
+
defaultRelayUrls,
|
|
368
|
+
fetchRemote: (args) => fetchRemoteState(args),
|
|
369
|
+
money,
|
|
370
|
+
stop
|
|
371
|
+
};
|
|
372
|
+
} catch (err) {
|
|
373
|
+
await stop().catch(() => void 0);
|
|
374
|
+
throw err;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function normalizeUrl(url) {
|
|
378
|
+
return url.replace(/\/+$/, "").replace(/\/ilp$/, "");
|
|
379
|
+
}
|
|
380
|
+
export {
|
|
381
|
+
MissingUplinkError,
|
|
382
|
+
chainFamilyOf,
|
|
383
|
+
createStandaloneContext,
|
|
384
|
+
defaultStoreDestinationFor,
|
|
385
|
+
readClientConfig,
|
|
386
|
+
readSolanaKeyFile,
|
|
387
|
+
resolveConnectorSettings,
|
|
388
|
+
resolveDestinations
|
|
389
|
+
};
|
|
390
|
+
//# sourceMappingURL=standalone-mode-KTYRTUS6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/standalone-mode.ts"],"sourcesContent":["/**\n * The standalone (embedded-client) session behind every paid command.\n *\n * One URL is the whole configuration. A TOON connector describes itself on\n * `GET /ilp` (connector ADR 0050): its ILP addresses, the routes it prices,\n * the chains it settles on and the key a payload is sealed to. The client\n * (`@toon-protocol/client` 2.x) reads that once, opens or adopts a payment\n * channel against the node, and pays each request with a signed claim. There\n * is nothing to discover and nothing to negotiate — kind:10032 announces were\n * removed by ADR 0046, and a node's word about itself replaced them.\n *\n * What this module resolves, in precedence order (env > config file):\n *\n * - `TOON_CONNECTOR` / `connectorUrl` — the node's client-edge URL. The\n * legacy `TOON_CLIENT_PROXY_URL` / `proxyUrl` (…`/ilp`) is still read; the\n * client normalizes a trailing `/ilp` away. `btpUrl` is ignored (the client\n * reads the BTP endpoint from `GET /ilp`).\n * - `TOON_CLIENT_PUBLISH_DESTINATION` / `publishDestination` — the route\n * events go to. Default: the first address the node publishes for itself\n * that it also prices (`defaultDestinationFor`).\n * - `TOON_CLIENT_STORE_DESTINATION` / `storeDestination` — the route git\n * objects go to. Default: the node's first priced route named `*.store`,\n * else `*.ario`. A `--via` override (`rig name`) outranks both.\n * - `TOON_CLIENT_STORE_CONNECTOR_URL` / `storeConnectorUrl` — when the store\n * route terminates on ANOTHER node that holds its own channel: that node's\n * URL. Uploads then ride a second client with its own watermark file.\n * `storeSealTo` alone (same channel, different terminating identity) makes\n * the edge's client seal to that node instead.\n * - `TOON_CLIENT_CHAIN` / `chain` — `evm` or `solana` (a full id such as\n * `evm:8453` is read by its family). Default: the first chain in the node's\n * `settlements[]` this identity holds a key for.\n * - `TOON_CLIENT_RPC_URL` / `rpcUrl` / `chainRpcUrls[<chain>]`.\n * - `RIG_SOLANA_KEY_FILE` / `solanaKeyFile`, `RIG_EVM_PRIVATE_KEY` /\n * `evmPrivateKey` — pay with THIS key instead of the phrase's derived one.\n * The author stays the phrase's Nostr key; who pays and who signs the\n * event are independent facts (the connector attributes payment from the\n * claim, never from the event).\n *\n * Key derivation: a rig phrase yields its Nostr key at `m/44'/1237'/0'/0/i`,\n * and its EVM account has always been that same secp256k1 key — which the\n * client calls `keyDerivation: 'legacy'`. That is the default here, so every\n * channel an existing identity funded is still its own. `keyDerivation:\n * 'standard'` / `TOON_CLIENT_KEY_DERIVATION=standard` opts into the\n * MetaMask-importable path for a fresh identity.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport {\n ToonClient,\n defaultDestinationFor,\n type ChannelState,\n type NodeSelfDescription,\n type ToonClientConfig,\n} from '@toon-protocol/client';\nimport { fetchRemoteState } from '../remote-state.js';\nimport {\n ChannelMapStore,\n RIG_CHANNEL_MAP_FILENAME,\n type ChannelMapRecord,\n} from '../standalone/channel-map.js';\nimport { ConnectorPublisher } from '../standalone/connector-publisher.js';\nimport type {\n ChannelCloseOutcome,\n ChannelOpenOutcome,\n ChannelSettleOutcome,\n StandaloneMoneyOps,\n WalletChainBalanceInfo,\n} from '../standalone/money.js';\nimport {\n NonceLock,\n checkDaemonIdentity,\n standaloneForced,\n} from '../standalone/nonce-guard.js';\nimport { deriveNostrKeyFromMnemonic } from '../standalone/nostr-identity.js';\nimport { resolveIdentity } from './identity.js';\nimport type {\n StandaloneContext,\n StandaloneLoadOptions,\n} from './standalone-context.js';\n\n// ---------------------------------------------------------------------------\n// Config file\n// ---------------------------------------------------------------------------\n\n/** The shared `~/.toon-client/config.json` fields rig reads. */\nexport interface ClientConfigFile {\n /** The connector's client-edge URL (`GET /ilp` lives here). */\n connectorUrl?: string;\n /** @deprecated the pre-2.0 spelling: the connector's `…/ilp` ingress URL. */\n proxyUrl?: string;\n /** @deprecated ignored — the client reads the BTP endpoint from `GET /ilp`. */\n btpUrl?: string;\n /** Free-read relay (WebSocket) for the identity's kind:0 and NIP-34 reads. */\n relayUrl?: string;\n /** @deprecated alias of `publishDestination`. */\n destination?: string;\n publishDestination?: string;\n storeDestination?: string;\n storeConnectorUrl?: string;\n storeSealTo?: string;\n feePerEvent?: string;\n channelStorePath?: string;\n /** `evm` | `solana`, or a full id (`evm:8453`) read by its family. */\n chain?: string;\n /** @deprecated pre-2.0 chain list; the first entry is read as `chain`. */\n supportedChains?: string[];\n rpcUrl?: string;\n chainRpcUrls?: Record<string, string>;\n transport?: 'auto' | 'http' | 'btp';\n keyDerivation?: 'standard' | 'legacy';\n mnemonicAccountIndex?: number;\n /** Collateral for a first channel open, base units. */\n deposit?: string;\n /** Path to a Solana keypair JSON (64-byte array) that pays instead of the phrase's key. */\n solanaKeyFile?: string;\n /** Hex EVM private key that pays instead of the phrase's key. */\n evmPrivateKey?: string;\n /** Per-packet timeout for uploads, ms. */\n uploadTimeoutMs?: number;\n [key: string]: unknown;\n}\n\nfunction configDir(env: NodeJS.ProcessEnv): string {\n return env['TOON_CLIENT_HOME'] ?? join(homedir(), '.toon-client');\n}\n\nexport function readClientConfig(path: string): ClientConfigFile {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as ClientConfigFile;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw new Error(\n `failed to read client config at ${path}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\n/** An identity was resolved, but there is no connector to pay. */\nexport class MissingUplinkError extends Error {\n constructor(configPath: string) {\n super(\n \"no connector configured: set TOON_CONNECTOR to the node's URL \" +\n '(the one whose GET /ilp describes it), run `rig entry <url>`, or add ' +\n `connectorUrl to ${configPath}`\n );\n this.name = 'MissingUplinkError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Settings (pure: env + file → what the client is built from)\n// ---------------------------------------------------------------------------\n\nexport type ChainFamily = 'evm' | 'solana';\n\nexport interface ConnectorSettings {\n connectorUrl?: string;\n connectorSource: 'env' | 'config' | 'legacy-env' | 'legacy-config' | null;\n publishDestination?: string;\n storeDestination?: string;\n storeConnectorUrl?: string;\n storeSealTo?: string;\n relayUrl?: string;\n chain?: ChainFamily;\n /** The full chain id the user named, when they named one (`evm:8453`). */\n chainKey?: string;\n rpcUrl?: string;\n transport: 'auto' | 'http' | 'btp';\n keyDerivation: 'standard' | 'legacy';\n channelStorePath: string;\n deposit?: bigint;\n eventFee: bigint;\n solanaKeyFile?: string;\n evmPrivateKey?: string;\n uploadTimeoutMs?: number;\n /** Notes for the caller's stderr: deprecated spellings met on the way. */\n warnings: string[];\n}\n\n/** `evm:8453` → `evm`; `solana` → `solana`; anything else → undefined. */\nexport function chainFamilyOf(\n chain: string | undefined\n): ChainFamily | undefined {\n if (!chain) return undefined;\n const family = chain.split(':')[0];\n return family === 'evm' || family === 'solana' ? family : undefined;\n}\n\nexport function resolveConnectorSettings(args: {\n env: NodeJS.ProcessEnv;\n file: ClientConfigFile;\n configDir: string;\n storeDestinationOverride?: string;\n}): ConnectorSettings {\n const { env, file } = args;\n const warnings: string[] = [];\n\n let connectorUrl: string | undefined;\n let connectorSource: ConnectorSettings['connectorSource'] = null;\n if (env['TOON_CONNECTOR']) {\n connectorUrl = env['TOON_CONNECTOR'];\n connectorSource = 'env';\n } else if (env['TOON_CLIENT_PROXY_URL']) {\n connectorUrl = env['TOON_CLIENT_PROXY_URL'];\n connectorSource = 'legacy-env';\n warnings.push(\n 'rig: TOON_CLIENT_PROXY_URL is the pre-2.0 spelling — set TOON_CONNECTOR to the node URL instead'\n );\n } else if (file.connectorUrl) {\n connectorUrl = file.connectorUrl;\n connectorSource = 'config';\n } else if (file.proxyUrl) {\n connectorUrl = file.proxyUrl;\n connectorSource = 'legacy-config';\n warnings.push(\n 'rig: config `proxyUrl` is the pre-2.0 spelling — `rig entry <url>` records `connectorUrl`'\n );\n }\n if (env['TOON_CLIENT_BTP_URL'] || file.btpUrl) {\n warnings.push(\n \"rig: btpUrl is ignored since 4.0 — the client reads the BTP endpoint from the node's GET /ilp\"\n );\n }\n\n const chainKey =\n env['TOON_CLIENT_CHAIN'] ?? file.chain ?? file.supportedChains?.[0];\n const chain = chainFamilyOf(chainKey);\n if (chainKey && !chain) {\n warnings.push(\n `rig: chain ${JSON.stringify(chainKey)} is not a settlement chain the connector supports (evm | solana) — ignoring it`\n );\n }\n const rpcUrl =\n env['TOON_CLIENT_RPC_URL'] ??\n file.rpcUrl ??\n (chainKey ? file.chainRpcUrls?.[chainKey] : undefined) ??\n (chain ? file.chainRpcUrls?.[chain] : undefined);\n\n const transportRaw = env['TOON_CLIENT_TRANSPORT'] ?? file.transport;\n const transport: ConnectorSettings['transport'] =\n transportRaw === 'http' || transportRaw === 'btp' ? transportRaw : 'auto';\n\n const derivationRaw = env['TOON_CLIENT_KEY_DERIVATION'] ?? file.keyDerivation;\n const keyDerivation: ConnectorSettings['keyDerivation'] =\n derivationRaw === 'standard' ? 'standard' : 'legacy';\n\n const depositRaw = env['TOON_CLIENT_DEPOSIT'] ?? file.deposit;\n const publishDestination =\n env['TOON_CLIENT_PUBLISH_DESTINATION'] ??\n env['TOON_CLIENT_DESTINATION'] ??\n file.publishDestination ??\n file.destination;\n const storeDestination =\n args.storeDestinationOverride ??\n env['TOON_CLIENT_STORE_DESTINATION'] ??\n file.storeDestination;\n const relayUrl = env['TOON_CLIENT_RELAY_URL'] ?? file.relayUrl;\n\n return {\n ...(connectorUrl ? { connectorUrl } : {}),\n connectorSource,\n ...(publishDestination ? { publishDestination } : {}),\n ...(storeDestination ? { storeDestination } : {}),\n ...((env['TOON_CLIENT_STORE_CONNECTOR_URL'] ?? file.storeConnectorUrl)\n ? {\n storeConnectorUrl:\n env['TOON_CLIENT_STORE_CONNECTOR_URL'] ?? file.storeConnectorUrl,\n }\n : {}),\n ...((env['TOON_CLIENT_STORE_SEAL_TO'] ?? file.storeSealTo)\n ? { storeSealTo: env['TOON_CLIENT_STORE_SEAL_TO'] ?? file.storeSealTo }\n : {}),\n ...(relayUrl ? { relayUrl } : {}),\n ...(chain ? { chain } : {}),\n ...(chainKey ? { chainKey } : {}),\n ...(rpcUrl ? { rpcUrl } : {}),\n transport,\n keyDerivation,\n channelStorePath:\n env['TOON_CLIENT_CHANNEL_STORE'] ??\n file.channelStorePath ??\n join(args.configDir, 'channels.json'),\n ...(depositRaw !== undefined ? { deposit: BigInt(depositRaw) } : {}),\n eventFee: BigInt(\n env['TOON_CLIENT_FEE_PER_EVENT'] ?? file.feePerEvent ?? '0'\n ),\n ...((env['RIG_SOLANA_KEY_FILE'] ?? file.solanaKeyFile)\n ? { solanaKeyFile: env['RIG_SOLANA_KEY_FILE'] ?? file.solanaKeyFile }\n : {}),\n ...((env['RIG_EVM_PRIVATE_KEY'] ?? file.evmPrivateKey)\n ? { evmPrivateKey: env['RIG_EVM_PRIVATE_KEY'] ?? file.evmPrivateKey }\n : {}),\n ...(file.uploadTimeoutMs !== undefined\n ? { uploadTimeoutMs: file.uploadTimeoutMs }\n : {}),\n warnings,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Destinations (pure: settings + the node's self-description)\n// ---------------------------------------------------------------------------\n\n/**\n * The store route a node offers, when the caller named none: its first priced\n * route spelled `*.store`, else `*.ario` (the AR.IO-backed store this\n * repository has always uploaded to), else nothing.\n */\nexport function defaultStoreDestinationFor(\n desc: NodeSelfDescription\n): string | undefined {\n const prefixes = desc.routes.map((r) => r.prefix);\n return (\n prefixes.find((p) => p === 'store' || p.endsWith('.store')) ??\n prefixes.find((p) => p === 'ario' || p.endsWith('.ario')) ??\n prefixes.find((p) => /store/i.test(p))\n );\n}\n\nexport function resolveDestinations(\n desc: NodeSelfDescription,\n settings: Pick<ConnectorSettings, 'publishDestination' | 'storeDestination'>,\n connectorUrl: string\n): { publish: string; store: string } {\n const publish = settings.publishDestination ?? defaultDestinationFor(desc);\n if (!publish) {\n throw new Error(\n `the connector at ${connectorUrl} publishes no priced address of its own — ` +\n 'set publishDestination / TOON_CLIENT_PUBLISH_DESTINATION to one of its GET /ilp routes'\n );\n }\n const store = settings.storeDestination ?? defaultStoreDestinationFor(desc);\n if (!store) {\n throw new Error(\n `the connector at ${connectorUrl} prices no store route (none named *.store or *.ario) — ` +\n 'set storeDestination / TOON_CLIENT_STORE_DESTINATION to the route its store answers on'\n );\n }\n return { publish, store };\n}\n\n// ---------------------------------------------------------------------------\n// Key files\n// ---------------------------------------------------------------------------\n\n/** A Solana keypair file: the 64-byte JSON array `solana-keygen` writes. */\nexport function readSolanaKeyFile(path: string): Uint8Array {\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n throw new Error(\n `failed to read the Solana key file ${path}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n if (\n !Array.isArray(parsed) ||\n (parsed.length !== 64 && parsed.length !== 32) ||\n !parsed.every((n) => Number.isInteger(n) && n >= 0 && n <= 255)\n ) {\n throw new Error(\n `${path} is not a Solana keypair file (expected a JSON array of 64 bytes)`\n );\n }\n return Uint8Array.from(parsed as number[]);\n}\n\n// ---------------------------------------------------------------------------\n// Money ops on the client's channel and wallet facades\n// ---------------------------------------------------------------------------\n\nfunction recordFor(\n state: ChannelState,\n args: { identity: string; destination: string; peerId: string }\n): Omit<ChannelMapRecord, 'openedAt' | 'lastUsedAt'> {\n const tokenNetwork =\n state.domain.tokenNetwork ?? state.domain.programId ?? '';\n return {\n channelId: state.channelId,\n peerId: args.peerId,\n identity: args.identity,\n destination: args.destination,\n chain: state.domain.chain,\n tokenNetwork,\n context: {\n chainType: state.chain,\n chainId: state.domain.chainId ?? 0,\n tokenNetworkAddress: tokenNetwork,\n tokenAddress: state.domain.token,\n recipient: state.domain.counterparty,\n },\n depositTotal: state.depositTotal.toString(),\n };\n}\n\nfunction buildMoneyOps(args: {\n client: ToonClient;\n channelMap: ChannelMapStore;\n identity: string;\n destination: string;\n peerId: string;\n}): StandaloneMoneyOps {\n const { client, channelMap } = args;\n const known = (channelId: string) =>\n channelMap.list().some((r) => r.channelId === channelId);\n const requireSameChannel = async (record: ChannelMapRecord) => {\n const state = await client.channel.state();\n if (state.channelId !== record.channelId) {\n throw new Error(\n `this identity's channel with ${args.destination} is ${state.channelId}, ` +\n `not the recorded ${record.channelId} — the record is stale or belongs to another node`\n );\n }\n };\n return {\n async openChannel(opts): Promise<ChannelOpenOutcome> {\n const before = await client.channel.state().catch(() => undefined);\n const resumed = before !== undefined && known(before.channelId);\n const state =\n opts?.deposit !== undefined && before?.status === 'open'\n ? await client.channel.deposit(opts.deposit)\n : await client.channel.open(\n opts?.deposit !== undefined ? { deposit: opts.deposit } : {}\n );\n channelMap.record(recordFor(state, args));\n return {\n channelId: state.channelId,\n resumed,\n destination: args.destination,\n chain: state.domain.chain,\n peerId: args.peerId,\n depositTotal: state.depositTotal.toString(),\n ...(opts?.deposit !== undefined\n ? { depositAdded: opts.deposit.toString() }\n : {}),\n };\n },\n async closeChannel(record): Promise<ChannelCloseOutcome> {\n await requireSameChannel(record);\n const result = await client.channel.close();\n const closedAt = result.closedAt ?? BigInt(Math.floor(Date.now() / 1000));\n return {\n channelId: record.channelId,\n ...(result.txHash ? { txHash: result.txHash } : {}),\n closedAt: closedAt.toString(),\n settleableAt: (result.settleableAt ?? closedAt).toString(),\n };\n },\n async settleChannel(record): Promise<ChannelSettleOutcome> {\n await requireSameChannel(record);\n const result = await client.channel.settle();\n channelMap.supersede(record);\n return {\n channelId: record.channelId,\n ...(result.txHash ? { txHash: result.txHash } : {}),\n };\n },\n async walletChainBalances(): Promise<WalletChainBalanceInfo[]> {\n return (await client.wallet.balances()).map((b) => ({\n chain: b.chain,\n chainKey: b.chainKey,\n address: b.address,\n ...(b.native ? { native: b.native } : {}),\n tokens: b.tokens,\n ...(b.unreadable !== undefined ? { unreadable: b.unreadable } : {}),\n ...(b.error !== undefined ? { error: b.error } : {}),\n }));\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// The context factory\n// ---------------------------------------------------------------------------\n\n/** What `rig balance` gets when nothing is configured to pay with. */\nclass UnconfiguredPublisher {\n constructor(private readonly error: MissingUplinkError) {}\n getFeeRates(): Promise<never> {\n return Promise.reject(this.error);\n }\n uploadGitObject(): Promise<never> {\n return Promise.reject(this.error);\n }\n publishEvent(): Promise<never> {\n return Promise.reject(this.error);\n }\n}\n\nexport async function createStandaloneContext(\n options: StandaloneLoadOptions\n): Promise<StandaloneContext> {\n const { env } = options;\n const warn = (line: string) => options.warn(line);\n const dir = configDir(env);\n const configPath = join(dir, 'config.json');\n const file = readClientConfig(configPath);\n const identity = await resolveIdentity(options);\n const nostr = deriveNostrKeyFromMnemonic(\n identity.mnemonic,\n identity.accountIndex\n );\n\n const settings = resolveConnectorSettings({\n env,\n file,\n configDir: dir,\n ...(options.storeDestination\n ? { storeDestinationOverride: options.storeDestination }\n : {}),\n });\n for (const line of settings.warnings) warn(line);\n const defaultRelayUrls = settings.relayUrl ? [settings.relayUrl] : [];\n\n if (!settings.connectorUrl) {\n const missing = new MissingUplinkError(configPath);\n if (options.requireUplink === false) {\n return {\n ownerPubkey: nostr.pubkey,\n identitySource: identity.source,\n identitySourceLabel: identity.sourceLabel,\n publisher: new UnconfiguredPublisher(missing),\n defaultRelayUrls,\n fetchRemote: (args) => fetchRemoteState(args),\n stop: () => Promise.resolve(),\n };\n }\n throw missing;\n }\n const connectorUrl = settings.connectorUrl;\n\n // One writer per identity per machine: two rig processes signing claims on\n // one channel race the nonce watermark, and the connector refuses the loser.\n if (!standaloneForced(env)) await checkDaemonIdentity(nostr.pubkey);\n const lock = await NonceLock.acquire(nostr.pubkey);\n\n const clients: ToonClient[] = [];\n const stop = async () => {\n for (const c of clients) await c.close();\n lock.release();\n };\n\n try {\n const payerKeys: Pick<\n ToonClientConfig,\n | 'mnemonic'\n | 'accountIndex'\n | 'keyDerivation'\n | 'solanaSecretKey'\n | 'evmPrivateKey'\n > = {\n mnemonic: identity.mnemonic,\n accountIndex: identity.accountIndex,\n keyDerivation: settings.keyDerivation,\n ...(settings.solanaKeyFile\n ? { solanaSecretKey: readSolanaKeyFile(settings.solanaKeyFile) }\n : {}),\n ...(settings.evmPrivateKey\n ? { evmPrivateKey: settings.evmPrivateKey }\n : {}),\n };\n const shared: Omit<ToonClientConfig, 'connector' | 'channelStore'> = {\n ...payerKeys,\n ...(settings.chain ? { chain: settings.chain } : {}),\n ...(settings.rpcUrl ? { rpcUrl: settings.rpcUrl } : {}),\n transport: settings.transport,\n autoOpenChannel: true,\n ...(settings.deposit !== undefined ? { deposit: settings.deposit } : {}),\n };\n if (settings.chain && !settings.rpcUrl) {\n warn(\n `rig: no rpcUrl configured for ${settings.chain} — the client falls back to its devnet preset; ` +\n 'set TOON_CLIENT_RPC_URL (or config rpcUrl) for a mainnet node'\n );\n }\n\n const client = await ToonClient.create({\n ...shared,\n connector: connectorUrl,\n channelStore: settings.channelStorePath,\n });\n clients.push(client);\n const desc = await client.describe();\n const destinations = resolveDestinations(desc, settings, connectorUrl);\n\n // The store leg: the same client unless the store terminates on another\n // node that holds its own channel (then its own client + watermark file).\n let storeClient = client;\n if (\n settings.storeConnectorUrl &&\n normalizeUrl(settings.storeConnectorUrl) !== normalizeUrl(connectorUrl)\n ) {\n storeClient = await ToonClient.create({\n ...shared,\n connector: settings.storeConnectorUrl,\n channelStore: settings.channelStorePath.replace(\n /(\\.json)?$/,\n '.store.json'\n ),\n });\n clients.push(storeClient);\n }\n\n const publisher = new ConnectorPublisher({\n publish: { client, destination: destinations.publish },\n store: {\n client: storeClient,\n destination: destinations.store,\n ...(storeClient === client && settings.storeSealTo\n ? { sealTo: settings.storeSealTo }\n : {}),\n },\n nostrSecretKey: nostr.secretKey,\n eventFee: settings.eventFee,\n ...(settings.uploadTimeoutMs !== undefined\n ? { uploadTimeoutMs: settings.uploadTimeoutMs }\n : {}),\n warn,\n });\n\n const channelMap = new ChannelMapStore({\n mapPath: join(dir, RIG_CHANNEL_MAP_FILENAME),\n watermarkPath: settings.channelStorePath,\n });\n const money = buildMoneyOps({\n client,\n channelMap,\n identity: nostr.pubkey,\n destination: options.channelDestination ?? destinations.publish,\n peerId: desc.edgeIdentity?.keyId ?? connectorUrl,\n });\n\n return {\n ownerPubkey: nostr.pubkey,\n identitySource: identity.source,\n identitySourceLabel: identity.sourceLabel,\n publisher,\n defaultRelayUrls,\n fetchRemote: (args) => fetchRemoteState(args),\n money,\n stop,\n };\n } catch (err) {\n await stop().catch(() => undefined);\n throw err;\n }\n}\n\nfunction normalizeUrl(url: string): string {\n return url.replace(/\\/+$/, '').replace(/\\/ilp$/, '');\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toon-protocol/rig",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Git-to-TOON write path core — build git objects and NIP-34 events for the Rig control plane",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonathan Green",
|
|
@@ -42,10 +42,13 @@
|
|
|
42
42
|
"README.md"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
+
"@scure/bip32": "^2.0.1",
|
|
46
|
+
"@scure/bip39": "^2.0.1",
|
|
45
47
|
"@toon-format/toon": "^1.0.0",
|
|
46
|
-
"@toon-protocol/client": "^
|
|
48
|
+
"@toon-protocol/client": "^2.1.1",
|
|
47
49
|
"@toon-protocol/core": "^3.2.0",
|
|
48
|
-
"nostr-tools": "^2.20.0"
|
|
50
|
+
"nostr-tools": "^2.20.0",
|
|
51
|
+
"viem": "^2.0.0"
|
|
49
52
|
},
|
|
50
53
|
"optionalDependencies": {
|
|
51
54
|
"@ar.io/sdk": "^4.0.3",
|
|
@@ -66,7 +69,6 @@
|
|
|
66
69
|
"build": "tsup",
|
|
67
70
|
"test": "vitest run",
|
|
68
71
|
"test:coverage": "vitest run --coverage",
|
|
69
|
-
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
70
|
-
"factory-job-proof": "tsx scripts/factory-job-proof.ts"
|
|
72
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
71
73
|
}
|
|
72
74
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/nip34-events.ts","../src/remote-state.ts","../../../node_modules/.pnpm/@toon-protocol+arweave@0.2.0/node_modules/@toon-protocol/arweave/src/gateways.ts","../../../node_modules/.pnpm/@toon-protocol+arweave@0.2.0/node_modules/@toon-protocol/arweave/src/git-sha.ts"],"sourcesContent":["/**\n * Pure NIP-34 event builders for the Git-to-TOON write path.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/event-builders.ts` (#223).\n * All builders return UnsignedEvent — the caller signs with their keypair\n * via finalizeEvent() and publishes through a Publisher (#226). Tag\n * structures follow the NIP-34 spec and `@toon-protocol/core/nip34`.\n */\n\nimport {\n ISSUE_KIND,\n PATCH_KIND,\n REPOSITORY_ANNOUNCEMENT_KIND,\n} from '@toon-protocol/core/nip34';\nimport type {\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n STATUS_OPEN_KIND,\n} from '@toon-protocol/core/nip34';\n\n// Kinds not (yet) exported by @toon-protocol/core/nip34:\n/** Repository State (refs) — replaceable, pairs with kind:30617 via `d` tag. */\nexport const REPOSITORY_STATE_KIND = 30618;\n/** Comment on an issue or patch (NIP-22 style threading within NIP-34). */\nexport const COMMENT_KIND = 1622;\n\n// ---------------------------------------------------------------------------\n// UnsignedEvent type (subset of nostr-tools — no id, sig, or pubkey)\n// ---------------------------------------------------------------------------\n\nexport interface UnsignedEvent {\n kind: number;\n content: string;\n tags: string[][];\n created_at: number;\n}\n\n// ---------------------------------------------------------------------------\n// kind:30617 — Repository Announcement (+ maintainer authority, #287)\n// ---------------------------------------------------------------------------\n\n/**\n * NIP-34 tag naming the repo's declared maintainers: one multi-valued tag\n * `[\"maintainers\", \"<hex-pubkey>\", \"<hex-pubkey>\", …]` on the kind:30617\n * announcement (mirrors the spec's multi-valued `relays` tag). The repo\n * OWNER — the announcement event's own pubkey — is ALWAYS an implicit\n * maintainer and need not be listed. Consumers derive an issue/PR's status\n * ONLY from kind:1630-1633 events signed by owner ∪ maintainers (#287): the\n * relay is permissionless, so this is the CONSUMER-side authority filter.\n */\nexport const MAINTAINERS_TAG = 'maintainers';\n\nconst HEX64 = /^[0-9a-f]{64}$/;\n\n/**\n * Collect the declared maintainer pubkeys (lowercased hex) from a kind:30617\n * event's tags. Tolerant of repeated `maintainers` tags and non-hex noise —\n * only 64-char hex values survive. Does NOT include the owner (implicit).\n */\nexport function parseMaintainers(tags: string[][]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n if (tag[0] !== MAINTAINERS_TAG) continue;\n for (const value of tag.slice(1)) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n out.push(hex);\n }\n }\n }\n return out;\n}\n\n/**\n * The set of pubkeys whose kind:1630-1633 status events are authoritative for\n * a repo: the owner (always) ∪ the declared maintainers (from the 30617's\n * `maintainers` tag). All values are lowercased hex.\n */\nexport function authorizedStatusAuthors(\n ownerPubkey: string,\n repoAnnouncementTags: string[][]\n): Set<string> {\n return new Set([\n ownerPubkey.toLowerCase(),\n ...parseMaintainers(repoAnnouncementTags),\n ]);\n}\n\n/**\n * Build a kind:30617 repository announcement event.\n *\n * @param repoId - Repository identifier (d tag)\n * @param name - Human-readable repository name\n * @param description - Repository description\n * @param maintainers - Optional declared maintainer pubkeys (hex). Emitted as\n * a single `[\"maintainers\", …]` tag when non-empty; duplicate and non-64-hex\n * values are dropped. The owner (the signer) is an implicit maintainer and\n * need not be listed — if passed it is emitted, which is harmless since the\n * owner is authorized regardless. See {@link MAINTAINERS_TAG}.\n */\nexport function buildRepoAnnouncement(\n repoId: string,\n name: string,\n description: string,\n maintainers: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['d', repoId],\n ['name', name],\n ['description', description],\n ];\n const declared: string[] = [];\n const seen = new Set<string>();\n for (const value of maintainers) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n declared.push(hex);\n }\n }\n if (declared.length > 0) {\n tags.push([MAINTAINERS_TAG, ...declared]);\n }\n return {\n kind: REPOSITORY_ANNOUNCEMENT_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:30618 — Repository Refs/State\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:30618 repository refs/state event.\n *\n * @param repoId - Repository identifier (d tag, matches kind:30617)\n * @param refs - Map of ref paths to commit SHAs (e.g., { 'refs/heads/main': 'abc123' })\n * @param arweaveMap - Map of git SHAs to Arweave transaction IDs\n */\nexport function buildRepoRefs(\n repoId: string,\n refs: Record<string, string>,\n arweaveMap: Record<string, string> = {}\n): UnsignedEvent {\n const tags: string[][] = [['d', repoId]];\n\n // Add ref tags\n for (const [refPath, commitSha] of Object.entries(refs)) {\n tags.push(['r', refPath, commitSha]);\n }\n\n // Default HEAD to first ref (typically refs/heads/main)\n const firstRef = Object.keys(refs)[0];\n if (firstRef) {\n tags.push(['HEAD', `ref: ${firstRef}`]);\n }\n\n // Add arweave SHA-to-txId mapping tags\n for (const [sha, txId] of Object.entries(arweaveMap)) {\n tags.push(['arweave', sha, txId]);\n }\n\n return {\n kind: REPOSITORY_STATE_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1621 — Issue\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1621 issue event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Issue title (subject tag)\n * @param body - Issue body (Markdown content)\n * @param labels - Optional labels (t tags)\n */\nexport function buildIssue(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n body: string,\n labels: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ...labels.map((label) => ['t', label]),\n ];\n\n return {\n kind: ISSUE_KIND,\n content: body,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1622 — Comment (on issue or PR)\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1622 comment event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param issueOrPrEventId - Event ID of the issue or PR being commented on\n * @param authorPubkey - Pubkey of the issue/PR author (NIP-34 `p` tag for threading), NOT the comment author\n * @param body - Comment body (Markdown content)\n * @param marker - Event reference marker: 'root' or 'reply' (default: 'reply')\n */\nexport function buildComment(\n repoOwnerPubkey: string,\n repoId: string,\n issueOrPrEventId: string,\n authorPubkey: string,\n body: string,\n marker: 'root' | 'reply' = 'reply'\n): UnsignedEvent {\n return {\n kind: COMMENT_KIND,\n content: body,\n tags: [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['e', issueOrPrEventId, '', marker],\n ['p', authorPubkey],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1617 — Patch / PR\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1617 patch event.\n *\n * The PR body/description travels in a dedicated `description` tag, NEVER in\n * `content` (#280): `content` is real `git format-patch` output that readers\n * pipe straight into `git am`, and git's patch-format detection hard-fails on\n * any leading prose (verified: \"Patch format detection failed.\"). The tag\n * route keeps `git am` consumption intact while `rig pr show` and the\n * rig-web/views `parsePR` renderers surface the description.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Patch/PR title (subject tag)\n * @param commits - Array of { sha, parentSha } for commit and parent-commit tags\n * @param branchTag - Branch name for the t tag\n * @param content - Real `git format-patch` text (NIP-34 patch body); defaults\n * to '' for callers that only reference commits by tag\n * @param description - PR body/cover text (`description` tag) — kept out of\n * `content` so `git am` still applies it\n */\nexport function buildPatch(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n commits: { sha: string; parentSha: string }[],\n branchTag?: string,\n content = '',\n description?: string\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ];\n\n if (description !== undefined && description !== '') {\n tags.push(['description', description]);\n }\n\n for (const commit of commits) {\n tags.push(['commit', commit.sha]);\n tags.push(['parent-commit', commit.parentSha]);\n }\n\n if (branchTag) {\n tags.push(['t', branchTag]);\n }\n\n return {\n kind: PATCH_KIND,\n content,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1630-1633 — Status\n// ---------------------------------------------------------------------------\n\n/** Status kinds: 1630 open, 1631 applied/merged, 1632 closed, 1633 draft. */\nexport type StatusKind =\n | typeof STATUS_OPEN_KIND\n | typeof STATUS_APPLIED_KIND\n | typeof STATUS_CLOSED_KIND\n | typeof STATUS_DRAFT_KIND;\n\n/**\n * Build a status event (kind 1630-1633).\n *\n * @param targetEventId - Event ID of the patch, PR, or issue being updated\n * @param statusKind - One of 1630 (open), 1631 (applied), 1632 (closed), 1633 (draft)\n * @param targetPubkey - Optional pubkey of the target event author (p tag per NIP-34 StatusEvent)\n */\nexport function buildStatus(\n targetEventId: string,\n statusKind: StatusKind,\n targetPubkey?: string\n): UnsignedEvent {\n const tags: string[][] = [['e', targetEventId]];\n if (targetPubkey) {\n tags.push(['p', targetPubkey]);\n }\n return {\n kind: statusKind,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n","/**\n * Remote repository state reader — the \"what does the remote have?\" half of\n * `rig push` (epic #222, ticket #225).\n *\n * Queries relay(s) over NIP-01 WebSocket for the repository's NIP-34 state:\n * - kind:30618 (repository state): `r` tags → ref map, `HEAD` symref,\n * `arweave` tags → git SHA → Arweave txId hints.\n * - kind:30617 (repository announcement): presence = the repo exists on\n * TOON (first-push detection) + name/description/relays metadata.\n *\n * Both kinds are NIP-33 parameterized-replaceable: per relay only the latest\n * event per (kind, author, d) survives, but we still pick the newest across\n * relays (highest created_at, ties broken by lowest id per NIP-01).\n *\n * SHAs missing from the `arweave` tag map can be resolved via the shared\n * Arweave GraphQL Git-SHA resolver (@toon-protocol/arweave — the same module\n * the rig SPA uses) through {@link RemoteState.resolveMissing}.\n *\n * Relay payload encodings (mirrors rig's `web/relay-client.ts` decode logic):\n * EVENT payloads arrive as an inline JSON object (standard NIP-01), as a\n * double-JSON-encoded string (devnet relay quirk: a JSON string containing\n * the event JSON), or as a TOON-encoded string. All three are tolerated.\n */\n\nimport { decode as decodeToon } from '@toon-format/toon';\nimport {\n resolveGitSha,\n seedShaCache,\n shaCacheKey,\n} from '@toon-protocol/arweave';\nimport { REPOSITORY_ANNOUNCEMENT_KIND } from '@toon-protocol/core/nip34';\n\nimport { REPOSITORY_STATE_KIND, parseMaintainers } from './nip34-events.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A signed Nostr event as seen on a relay (read side). */\nexport interface NostrEvent {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** NIP-01 subscription filter (only the fields rig's readers send). */\nexport interface NostrFilter {\n ids?: string[];\n kinds?: number[];\n authors?: string[];\n '#d'?: string[];\n /** Repo address tag filter, e.g. `30617:<owner>:<repoId>` (#278 tracker). */\n '#a'?: string[];\n /** Event-reference tag filter (#278 tracker: statuses + comments). */\n '#e'?: string[];\n limit?: number;\n}\n\n/**\n * Minimal structural WebSocket type — satisfied by the WHATWG WebSocket\n * global (Node >= 22 / undici, browsers) and by the `ws` package.\n */\nexport interface WebSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: string, listener: (event: never) => void): void;\n}\n\n/** Factory for WebSocket connections (injectable for tests / `ws` fallback). */\nexport type WebSocketFactory = (url: string) => WebSocketLike;\n\nexport interface FetchRemoteStateOptions {\n /**\n * Relay WebSocket URLs to query. Plural from day one (forward-compat with\n * multi-relay routing); size 1 is typical today. Results are merged and the\n * newest replaceable event across all relays wins.\n */\n relayUrls: string[];\n /** Repository owner's pubkey (hex) — the author of 30617/30618. */\n ownerPubkey: string;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n /** Per-relay timeout in milliseconds (default 10000). On timeout the relay contributes whatever it sent so far. */\n timeoutMs?: number;\n /**\n * Git-SHA → Arweave txId resolver used by {@link RemoteState.resolveMissing}.\n * Defaults to the shared GraphQL resolver from @toon-protocol/arweave;\n * injectable for tests.\n */\n resolveSha?: (sha: string, repo: string) => Promise<string | null>;\n /** WebSocket constructor override (defaults to the global WebSocket). */\n webSocketFactory?: WebSocketFactory;\n}\n\n/** Remote repository state assembled from relay events. */\nexport interface RemoteState {\n /** True when a kind:30617 announcement exists (false ⇒ first push). */\n announced: boolean;\n /** Ref map from the latest kind:30618: refname → commit SHA. */\n refs: Map<string, string>;\n /** HEAD symref target (e.g. `refs/heads/main`), or null if unset. */\n headSymref: string | null;\n /** Git SHA → Arweave txId hints from the latest kind:30618 `arweave` tags. */\n shaToTxId: Map<string, string>;\n /** The latest kind:30618 event, or null if the repo has no state yet. */\n refsEvent: NostrEvent | null;\n /** The latest kind:30617 event, or null if the repo is unannounced. */\n announceEvent: NostrEvent | null;\n /** Repository name from the announcement `name` tag. */\n name: string | null;\n /** Announcement `description` tag (falls back to event content). */\n description: string | null;\n /** Relay URLs advertised in the announcement `relays` tag(s). */\n relays: string[];\n /**\n * Declared maintainer pubkeys (hex) from the announcement `maintainers`\n * tag (#287). Does NOT include the owner (an implicit maintainer). Empty\n * when unannounced or owner-only.\n */\n maintainers: string[];\n /**\n * Resolve SHAs to Arweave txIds: served from the `arweave` tag map when\n * present, otherwise via the GraphQL Git-SHA resolver. SHAs that resolve\n * nowhere are omitted from the returned map.\n */\n resolveMissing(shas: string[]): Promise<Map<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// Relay query (NIP-01 REQ → EVENT* → EOSE)\n// ---------------------------------------------------------------------------\n\n/** Validate that a relay URL uses a WebSocket scheme (mirrors rig's url-utils). */\nfunction isValidRelayUrl(url: string): boolean {\n return /^wss?:\\/\\//i.test(url);\n}\n\n/** WHATWG WebSocket OPEN ready state. */\nconst WS_OPEN = 1;\n\nfunction defaultWebSocketFactory(url: string): WebSocketLike {\n const ctor = (\n globalThis as { WebSocket?: new (url: string) => WebSocketLike }\n ).WebSocket;\n if (!ctor) {\n throw new Error(\n 'No global WebSocket constructor (Node >= 22 required) — pass webSocketFactory'\n );\n }\n return new ctor(url);\n}\n\n/**\n * Decode a relay EVENT payload, tolerating every encoding seen in the wild:\n * inline object (standard NIP-01), double-JSON-encoded string (devnet relay\n * serves the event as a JSON string containing the event JSON), or a\n * TOON-encoded string (rig's `decodeToonMessage` path).\n */\nfunction decodeEventPayload(payload: unknown): NostrEvent | null {\n if (payload !== null && typeof payload === 'object') {\n return payload as NostrEvent;\n }\n if (typeof payload !== 'string') {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(payload);\n if (parsed !== null && typeof parsed === 'object') {\n return parsed as NostrEvent;\n }\n } catch {\n // Not JSON — fall through to TOON\n }\n try {\n return decodeToon(payload) as unknown as NostrEvent;\n } catch {\n return null;\n }\n}\n\n/**\n * Query one relay: send a REQ, collect EVENTs until EOSE, then CLOSE.\n * Mirrors rig's `queryRelay` (partial results on timeout / early close).\n * Exported for reuse by the network-bootstrap discovery (kind:10032).\n */\nexport function queryRelay(\n relayUrl: string,\n filter: NostrFilter,\n timeoutMs: number,\n webSocketFactory: WebSocketFactory\n): Promise<NostrEvent[]> {\n return new Promise((resolve, reject) => {\n const events: NostrEvent[] = [];\n const subId = `git-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n let ws: WebSocketLike;\n // eslint-disable-next-line prefer-const -- assigned after `settle` is defined\n let timeoutHandle: ReturnType<typeof setTimeout>;\n let settled = false;\n\n const settle = (outcome: 'resolve' | 'reject', error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n try {\n if (ws.readyState === WS_OPEN) {\n ws.send(JSON.stringify(['CLOSE', subId]));\n }\n ws.close();\n } catch {\n // Ignore close errors\n }\n if (outcome === 'reject') {\n reject(error);\n } else {\n resolve(events);\n }\n };\n\n if (!isValidRelayUrl(relayUrl)) {\n reject(\n new Error(\n `Invalid relay URL protocol (must be ws:// or wss://): ${relayUrl}`\n )\n );\n return;\n }\n\n try {\n ws = webSocketFactory(relayUrl);\n } catch (err) {\n reject(new Error(`Failed to connect to relay ${relayUrl}: ${String(err)}`));\n return;\n }\n\n timeoutHandle = setTimeout(() => {\n // Resolve with whatever we collected so far (partial results)\n settle('resolve');\n }, timeoutMs);\n\n ws.addEventListener('open', () => {\n ws.send(JSON.stringify(['REQ', subId, filter]));\n });\n\n ws.addEventListener('message', (msgEvent: { data?: unknown }) => {\n try {\n const msg = JSON.parse(String(msgEvent.data)) as unknown[];\n if (!Array.isArray(msg) || msg.length < 2) return;\n\n const msgType = msg[0];\n if (msgType === 'EVENT' && msg[1] === subId && msg[2] !== undefined) {\n const event = decodeEventPayload(msg[2]);\n if (event) events.push(event);\n } else if (msgType === 'EOSE' && msg[1] === subId) {\n settle('resolve');\n }\n } catch {\n // Ignore parse errors for individual messages\n }\n });\n\n ws.addEventListener('error', (event: { message?: unknown }) => {\n const detail =\n typeof event === 'object' && event !== null && 'message' in event\n ? String(event.message)\n : 'unknown';\n settle(\n 'reject',\n new Error(`WebSocket error connecting to ${relayUrl}: ${detail}`)\n );\n });\n\n ws.addEventListener('close', () => {\n // If we haven't settled yet, resolve with what we have\n settle('resolve');\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// NIP-33 replaceable selection + tag parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Pick the winning replaceable event: highest created_at, ties broken by\n * lowest id (NIP-01 replaceable-event convention).\n */\nfunction latestReplaceable(events: NostrEvent[]): NostrEvent | null {\n let winner: NostrEvent | null = null;\n for (const event of events) {\n if (\n winner === null ||\n event.created_at > winner.created_at ||\n (event.created_at === winner.created_at && event.id < winner.id)\n ) {\n winner = event;\n }\n }\n return winner;\n}\n\n/** Get the first value for a tag name. */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n const tag = tags.find((t) => t[0] === name);\n return tag?.[1];\n}\n\n/** Maximum number of refs parsed from a single kind:30618 event (mirrors views). */\nconst MAX_REFS_PER_EVENT = 1000;\n\n/** Symref prefix used in `HEAD` tags: `[\"HEAD\", \"ref: refs/heads/main\"]`. */\nconst SYMREF_PREFIX = 'ref: ';\n\ninterface ParsedRefs {\n refs: Map<string, string>;\n headSymref: string | null;\n shaToTxId: Map<string, string>;\n}\n\n/** Parse a kind:30618 event's `r` / `HEAD` / `arweave` tags. */\nfunction parseRefsEvent(event: NostrEvent): ParsedRefs {\n const refs = new Map<string, string>();\n const shaToTxId = new Map<string, string>();\n let headSymref: string | null = null;\n\n for (const tag of event.tags) {\n const [tagName, v1, v2] = tag;\n if (tagName === 'r' && v1 && v2) {\n if (v1 === 'HEAD' && v2.startsWith(SYMREF_PREFIX)) {\n // Alternate symref spelling: [\"r\", \"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v2.slice(SYMREF_PREFIX.length);\n continue;\n }\n if (refs.size >= MAX_REFS_PER_EVENT) continue;\n refs.set(v1, v2);\n } else if (tagName === 'HEAD' && v1?.startsWith(SYMREF_PREFIX)) {\n // NIP-34 symref tag: [\"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v1.slice(SYMREF_PREFIX.length);\n } else if (tagName === 'arweave' && v1 && v2) {\n shaToTxId.set(v1, v2);\n }\n }\n\n return { refs, headSymref, shaToTxId };\n}\n\n// ---------------------------------------------------------------------------\n// fetchRemoteState\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch the remote repository state from the relay(s).\n *\n * Sends one REQ per relay for kind:30617 + kind:30618 with\n * `authors=[ownerPubkey]`, `#d=[repoId]`, collects until EOSE (or timeout),\n * and reduces to the latest replaceable event per kind.\n *\n * Resolves as long as at least one relay answers; throws only when every\n * relay fails. Events from other authors / repos are ignored (defense\n * against misbehaving relays that over-return).\n */\nexport async function fetchRemoteState(\n options: FetchRemoteStateOptions\n): Promise<RemoteState> {\n const {\n relayUrls,\n ownerPubkey,\n repoId,\n timeoutMs = 10000,\n resolveSha = resolveGitSha,\n webSocketFactory = defaultWebSocketFactory,\n } = options;\n\n if (relayUrls.length === 0) {\n throw new Error('fetchRemoteState: relayUrls must not be empty');\n }\n if (!ownerPubkey) {\n throw new Error('fetchRemoteState: ownerPubkey is required');\n }\n if (!repoId) {\n throw new Error('fetchRemoteState: repoId is required');\n }\n\n const filter: NostrFilter = {\n kinds: [REPOSITORY_ANNOUNCEMENT_KIND, REPOSITORY_STATE_KIND],\n authors: [ownerPubkey],\n '#d': [repoId],\n };\n\n const results = await Promise.allSettled(\n relayUrls.map((url) => queryRelay(url, filter, timeoutMs, webSocketFactory))\n );\n\n const failures: string[] = [];\n const byId = new Map<string, NostrEvent>();\n for (const result of results) {\n if (result.status === 'rejected') {\n failures.push(String((result.reason as Error)?.message ?? result.reason));\n continue;\n }\n for (const event of result.value) {\n // Only trust events from the repo owner for this repo — relays are\n // untrusted and may over-return.\n if (event.pubkey !== ownerPubkey) continue;\n if (getTagValue(event.tags, 'd') !== repoId) continue;\n if (typeof event.id === 'string' && !byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n }\n\n if (failures.length === relayUrls.length) {\n throw new Error(\n `fetchRemoteState: all ${relayUrls.length} relay(s) failed: ${failures.join('; ')}`\n );\n }\n\n const events = [...byId.values()];\n const refsEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_STATE_KIND)\n );\n const announceEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND)\n );\n\n const { refs, headSymref, shaToTxId } = refsEvent\n ? parseRefsEvent(refsEvent)\n : {\n refs: new Map<string, string>(),\n headSymref: null,\n shaToTxId: new Map<string, string>(),\n };\n\n // Seed the shared resolver cache so later resolveGitSha calls (here or in\n // any other consumer of @toon-protocol/arweave) skip GraphQL for known SHAs.\n if (shaToTxId.size > 0) {\n seedShaCache(\n [...shaToTxId].map(\n ([sha, txId]) => [shaCacheKey(sha, repoId), txId] as [string, string]\n )\n );\n }\n\n // Announcement metadata (mirrors views' parseRepoAnnouncement defaults).\n const name = announceEvent\n ? (getTagValue(announceEvent.tags, 'name') ?? null)\n : null;\n const description = announceEvent\n ? (getTagValue(announceEvent.tags, 'description') ?? announceEvent.content)\n : null;\n // NIP-34 announcement relays tag is multi-valued: [\"relays\", url1, url2, …]\n const relays: string[] = [];\n if (announceEvent) {\n for (const tag of announceEvent.tags) {\n if (tag[0] === 'relays') {\n relays.push(...tag.slice(1).filter((url) => url.length > 0));\n }\n }\n }\n // Declared maintainers (#287): the `maintainers` tag on the 30617. Owner is\n // an implicit maintainer and is NOT listed here.\n const maintainers = announceEvent\n ? parseMaintainers(announceEvent.tags)\n : [];\n\n const resolveMissing = async (\n shas: string[]\n ): Promise<Map<string, string>> => {\n const resolved = new Map<string, string>();\n const missing: string[] = [];\n for (const sha of new Set(shas)) {\n const known = shaToTxId.get(sha);\n if (known !== undefined) {\n resolved.set(sha, known);\n } else {\n missing.push(sha);\n }\n }\n const lookups = await Promise.all(\n missing.map(\n async (sha) => [sha, await resolveSha(sha, repoId)] as const\n )\n );\n for (const [sha, txId] of lookups) {\n if (txId) resolved.set(sha, txId);\n }\n return resolved;\n };\n\n return {\n announced: announceEvent !== null,\n refs,\n headSymref,\n shaToTxId,\n refsEvent,\n announceEvent,\n name,\n description,\n relays,\n maintainers,\n resolveMissing,\n };\n}\n",null,null],"mappings":"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","names":["REPOSITORY_ANNOUNCEMENT_KIND","REPOSITORY_ANNOUNCEMENT_KIND"]}
|