@toon-protocol/rig 3.6.0 → 4.0.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 -152
- 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-LCYCKWAT.js → chunk-YZRR2UO6.js} +86 -5
- package/dist/chunk-YZRR2UO6.js.map +1 -0
- package/dist/{chunk-HJDPM37T.js → chunk-ZZBA5VVL.js} +11 -7
- package/dist/{chunk-HJDPM37T.js.map → chunk-ZZBA5VVL.js.map} +1 -1
- package/dist/cli/rig.js +290 -523
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +3 -115
- package/dist/index.js +9 -121
- package/dist/index.js.map +1 -1
- package/dist/{publisher-BFcnZeJI.d.ts → publisher-CcL96ugq.d.ts} +58 -1
- package/dist/standalone/index.d.ts +241 -438
- package/dist/standalone/index.js +19 -11
- package/dist/standalone-mode-LEAC4A2L.js +390 -0
- package/dist/standalone-mode-LEAC4A2L.js.map +1 -0
- package/package.json +5 -4
- 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-2KJJ7LSO.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-LEAC4A2L.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":";;;;;;;;;;;;;;;;;;;;AA8CA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAqEP,SAAS,UAAU,KAAgC;AACjD,SAAO,IAAI,kBAAkB,KAAK,KAAK,QAAQ,GAAG,cAAc;AAClE;AAEO,SAAS,iBAAiB,MAAgC;AAC/D,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM,IAAI;AAAA,MACR,mCAAmC,IAAI,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC9F;AAAA,EACF;AACF;AAGO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YAAY,YAAoB;AAC9B;AAAA,MACE,wJAEqB,UAAU;AAAA,IACjC;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAiCO,SAAS,cACd,OACyB;AACzB,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC;AACjC,SAAO,WAAW,SAAS,WAAW,WAAW,SAAS;AAC5D;AAEO,SAAS,yBAAyB,MAKnB;AACpB,QAAM,EAAE,KAAK,KAAK,IAAI;AACtB,QAAM,WAAqB,CAAC;AAE5B,MAAI;AACJ,MAAI,kBAAwD;AAC5D,MAAI,IAAI,gBAAgB,GAAG;AACzB,mBAAe,IAAI,gBAAgB;AACnC,sBAAkB;AAAA,EACpB,WAAW,IAAI,uBAAuB,GAAG;AACvC,mBAAe,IAAI,uBAAuB;AAC1C,sBAAkB;AAClB,aAAS;AAAA,MACP;AAAA,IACF;AAAA,EACF,WAAW,KAAK,cAAc;AAC5B,mBAAe,KAAK;AACpB,sBAAkB;AAAA,EACpB,WAAW,KAAK,UAAU;AACxB,mBAAe,KAAK;AACpB,sBAAkB;AAClB,aAAS;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACA,MAAI,IAAI,qBAAqB,KAAK,KAAK,QAAQ;AAC7C,aAAS;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WACJ,IAAI,mBAAmB,KAAK,KAAK,SAAS,KAAK,kBAAkB,CAAC;AACpE,QAAM,QAAQ,cAAc,QAAQ;AACpC,MAAI,YAAY,CAAC,OAAO;AACtB,aAAS;AAAA,MACP,cAAc,KAAK,UAAU,QAAQ,CAAC;AAAA,IACxC;AAAA,EACF;AACA,QAAM,SACJ,IAAI,qBAAqB,KACzB,KAAK,WACJ,WAAW,KAAK,eAAe,QAAQ,IAAI,YAC3C,QAAQ,KAAK,eAAe,KAAK,IAAI;AAExC,QAAM,eAAe,IAAI,uBAAuB,KAAK,KAAK;AAC1D,QAAM,YACJ,iBAAiB,UAAU,iBAAiB,QAAQ,eAAe;AAErE,QAAM,gBAAgB,IAAI,4BAA4B,KAAK,KAAK;AAChE,QAAM,gBACJ,kBAAkB,aAAa,aAAa;AAE9C,QAAM,aAAa,IAAI,qBAAqB,KAAK,KAAK;AACtD,QAAM,qBACJ,IAAI,iCAAiC,KACrC,IAAI,yBAAyB,KAC7B,KAAK,sBACL,KAAK;AACP,QAAM,mBACJ,KAAK,4BACL,IAAI,+BAA+B,KACnC,KAAK;AACP,QAAM,WAAW,IAAI,uBAAuB,KAAK,KAAK;AAEtD,SAAO;AAAA,IACL,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,IACvC;AAAA,IACA,GAAI,qBAAqB,EAAE,mBAAmB,IAAI,CAAC;AAAA,IACnD,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;AAAA,IAC/C,GAAK,IAAI,iCAAiC,KAAK,KAAK,oBAChD;AAAA,MACE,mBACE,IAAI,iCAAiC,KAAK,KAAK;AAAA,IACnD,IACA,CAAC;AAAA,IACL,GAAK,IAAI,2BAA2B,KAAK,KAAK,cAC1C,EAAE,aAAa,IAAI,2BAA2B,KAAK,KAAK,YAAY,IACpE,CAAC;AAAA,IACL,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IACzB,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,IAC3B;AAAA,IACA;AAAA,IACA,kBACE,IAAI,2BAA2B,KAC/B,KAAK,oBACL,KAAK,KAAK,WAAW,eAAe;AAAA,IACtC,GAAI,eAAe,SAAY,EAAE,SAAS,OAAO,UAAU,EAAE,IAAI,CAAC;AAAA,IAClE,UAAU;AAAA,MACR,IAAI,2BAA2B,KAAK,KAAK,eAAe;AAAA,IAC1D;AAAA,IACA,GAAK,IAAI,qBAAqB,KAAK,KAAK,gBACpC,EAAE,eAAe,IAAI,qBAAqB,KAAK,KAAK,cAAc,IAClE,CAAC;AAAA,IACL,GAAK,IAAI,qBAAqB,KAAK,KAAK,gBACpC,EAAE,eAAe,IAAI,qBAAqB,KAAK,KAAK,cAAc,IAClE,CAAC;AAAA,IACL,GAAI,KAAK,oBAAoB,SACzB,EAAE,iBAAiB,KAAK,gBAAgB,IACxC,CAAC;AAAA,IACL;AAAA,EACF;AACF;AAWO,SAAS,2BACd,MACoB;AACpB,QAAM,WAAW,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM;AAChD,SACE,SAAS,KAAK,CAAC,MAAM,MAAM,WAAW,EAAE,SAAS,QAAQ,CAAC,KAC1D,SAAS,KAAK,CAAC,MAAM,MAAM,UAAU,EAAE,SAAS,OAAO,CAAC,KACxD,SAAS,KAAK,CAAC,MAAM,SAAS,KAAK,CAAC,CAAC;AAEzC;AAEO,SAAS,oBACd,MACA,UACA,cACoC;AACpC,QAAM,UAAU,SAAS,sBAAsB,sBAAsB,IAAI;AACzE,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,oBAAoB,YAAY;AAAA,IAElC;AAAA,EACF;AACA,QAAM,QAAQ,SAAS,oBAAoB,2BAA2B,IAAI;AAC1E,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,oBAAoB,YAAY;AAAA,IAElC;AAAA,EACF;AACA,SAAO,EAAE,SAAS,MAAM;AAC1B;AAOO,SAAS,kBAAkB,MAA0B;AAC1D,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAChD,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,sCAAsC,IAAI,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACjG;AAAA,EACF;AACA,MACE,CAAC,MAAM,QAAQ,MAAM,KACpB,OAAO,WAAW,MAAM,OAAO,WAAW,MAC3C,CAAC,OAAO,MAAM,CAAC,MAAM,OAAO,UAAU,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,GAC9D;AACA,UAAM,IAAI;AAAA,MACR,GAAG,IAAI;AAAA,IACT;AAAA,EACF;AACA,SAAO,WAAW,KAAK,MAAkB;AAC3C;AAMA,SAAS,UACP,OACA,MACmD;AACnD,QAAM,eACJ,MAAM,OAAO,gBAAgB,MAAM,OAAO,aAAa;AACzD,SAAO;AAAA,IACL,WAAW,MAAM;AAAA,IACjB,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,IACf,aAAa,KAAK;AAAA,IAClB,OAAO,MAAM,OAAO;AAAA,IACpB;AAAA,IACA,SAAS;AAAA,MACP,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM,OAAO,WAAW;AAAA,MACjC,qBAAqB;AAAA,MACrB,cAAc,MAAM,OAAO;AAAA,MAC3B,WAAW,MAAM,OAAO;AAAA,IAC1B;AAAA,IACA,cAAc,MAAM,aAAa,SAAS;AAAA,EAC5C;AACF;AAEA,SAAS,cAAc,MAMA;AACrB,QAAM,EAAE,QAAQ,WAAW,IAAI;AAC/B,QAAM,QAAQ,CAAC,cACb,WAAW,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS;AACzD,QAAM,qBAAqB,OAAO,WAA6B;AAC7D,UAAM,QAAQ,MAAM,OAAO,QAAQ,MAAM;AACzC,QAAI,MAAM,cAAc,OAAO,WAAW;AACxC,YAAM,IAAI;AAAA,QACR,gCAAgC,KAAK,WAAW,OAAO,MAAM,SAAS,sBAChD,OAAO,SAAS;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,MAAM,YAAY,MAAmC;AACnD,YAAM,SAAS,MAAM,OAAO,QAAQ,MAAM,EAAE,MAAM,MAAM,MAAS;AACjE,YAAM,UAAU,WAAW,UAAa,MAAM,OAAO,SAAS;AAC9D,YAAM,QACJ,MAAM,YAAY,UAAa,QAAQ,WAAW,SAC9C,MAAM,OAAO,QAAQ,QAAQ,KAAK,OAAO,IACzC,MAAM,OAAO,QAAQ;AAAA,QACnB,MAAM,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAC7D;AACN,iBAAW,OAAO,UAAU,OAAO,IAAI,CAAC;AACxC,aAAO;AAAA,QACL,WAAW,MAAM;AAAA,QACjB;AAAA,QACA,aAAa,KAAK;AAAA,QAClB,OAAO,MAAM,OAAO;AAAA,QACpB,QAAQ,KAAK;AAAA,QACb,cAAc,MAAM,aAAa,SAAS;AAAA,QAC1C,GAAI,MAAM,YAAY,SAClB,EAAE,cAAc,KAAK,QAAQ,SAAS,EAAE,IACxC,CAAC;AAAA,MACP;AAAA,IACF;AAAA,IACA,MAAM,aAAa,QAAsC;AACvD,YAAM,mBAAmB,MAAM;AAC/B,YAAM,SAAS,MAAM,OAAO,QAAQ,MAAM;AAC1C,YAAM,WAAW,OAAO,YAAY,OAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;AACxE,aAAO;AAAA,QACL,WAAW,OAAO;AAAA,QAClB,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,QACjD,UAAU,SAAS,SAAS;AAAA,QAC5B,eAAe,OAAO,gBAAgB,UAAU,SAAS;AAAA,MAC3D;AAAA,IACF;AAAA,IACA,MAAM,cAAc,QAAuC;AACzD,YAAM,mBAAmB,MAAM;AAC/B,YAAM,SAAS,MAAM,OAAO,QAAQ,OAAO;AAC3C,iBAAW,UAAU,MAAM;AAC3B,aAAO;AAAA,QACL,WAAW,OAAO;AAAA,QAClB,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,IACA,MAAM,sBAAyD;AAC7D,cAAQ,MAAM,OAAO,OAAO,SAAS,GAAG,IAAI,CAAC,OAAO;AAAA,QAClD,OAAO,EAAE;AAAA,QACT,UAAU,EAAE;AAAA,QACZ,SAAS,EAAE;AAAA,QACX,GAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,QACvC,QAAQ,EAAE;AAAA,QACV,GAAI,EAAE,eAAe,SAAY,EAAE,YAAY,EAAE,WAAW,IAAI,CAAC;AAAA,QACjE,GAAI,EAAE,UAAU,SAAY,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAAA,MACpD,EAAE;AAAA,IACJ;AAAA,EACF;AACF;AAOA,IAAM,wBAAN,MAA4B;AAAA,EAC1B,YAA6B,OAA2B;AAA3B;AAAA,EAA4B;AAAA,EAA5B;AAAA,EAC7B,cAA8B;AAC5B,WAAO,QAAQ,OAAO,KAAK,KAAK;AAAA,EAClC;AAAA,EACA,kBAAkC;AAChC,WAAO,QAAQ,OAAO,KAAK,KAAK;AAAA,EAClC;AAAA,EACA,eAA+B;AAC7B,WAAO,QAAQ,OAAO,KAAK,KAAK;AAAA,EAClC;AACF;AAEA,eAAsB,wBACpB,SAC4B;AAC5B,QAAM,EAAE,IAAI,IAAI;AAChB,QAAM,OAAO,CAAC,SAAiB,QAAQ,KAAK,IAAI;AAChD,QAAM,MAAM,UAAU,GAAG;AACzB,QAAM,aAAa,KAAK,KAAK,aAAa;AAC1C,QAAM,OAAO,iBAAiB,UAAU;AACxC,QAAM,WAAW,MAAM,gBAAgB,OAAO;AAC9C,QAAM,QAAQ;AAAA,IACZ,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAEA,QAAM,WAAW,yBAAyB;AAAA,IACxC;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,GAAI,QAAQ,mBACR,EAAE,0BAA0B,QAAQ,iBAAiB,IACrD,CAAC;AAAA,EACP,CAAC;AACD,aAAW,QAAQ,SAAS,SAAU,MAAK,IAAI;AAC/C,QAAM,mBAAmB,SAAS,WAAW,CAAC,SAAS,QAAQ,IAAI,CAAC;AAEpE,MAAI,CAAC,SAAS,cAAc;AAC1B,UAAM,UAAU,IAAI,mBAAmB,UAAU;AACjD,QAAI,QAAQ,kBAAkB,OAAO;AACnC,aAAO;AAAA,QACL,aAAa,MAAM;AAAA,QACnB,gBAAgB,SAAS;AAAA,QACzB,qBAAqB,SAAS;AAAA,QAC9B,WAAW,IAAI,sBAAsB,OAAO;AAAA,QAC5C;AAAA,QACA,aAAa,CAAC,SAAS,iBAAiB,IAAI;AAAA,QAC5C,MAAM,MAAM,QAAQ,QAAQ;AAAA,MAC9B;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACA,QAAM,eAAe,SAAS;AAI9B,MAAI,CAAC,iBAAiB,GAAG,EAAG,OAAM,oBAAoB,MAAM,MAAM;AAClE,QAAM,OAAO,MAAM,UAAU,QAAQ,MAAM,MAAM;AAEjD,QAAM,UAAwB,CAAC;AAC/B,QAAM,OAAO,YAAY;AACvB,eAAW,KAAK,QAAS,OAAM,EAAE,MAAM;AACvC,SAAK,QAAQ;AAAA,EACf;AAEA,MAAI;AACF,UAAM,YAOF;AAAA,MACF,UAAU,SAAS;AAAA,MACnB,cAAc,SAAS;AAAA,MACvB,eAAe,SAAS;AAAA,MACxB,GAAI,SAAS,gBACT,EAAE,iBAAiB,kBAAkB,SAAS,aAAa,EAAE,IAC7D,CAAC;AAAA,MACL,GAAI,SAAS,gBACT,EAAE,eAAe,SAAS,cAAc,IACxC,CAAC;AAAA,IACP;AACA,UAAM,SAA+D;AAAA,MACnE,GAAG;AAAA,MACH,GAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;AAAA,MAClD,GAAI,SAAS,SAAS,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;AAAA,MACrD,WAAW,SAAS;AAAA,MACpB,iBAAiB;AAAA,MACjB,GAAI,SAAS,YAAY,SAAY,EAAE,SAAS,SAAS,QAAQ,IAAI,CAAC;AAAA,IACxE;AACA,QAAI,SAAS,SAAS,CAAC,SAAS,QAAQ;AACtC;AAAA,QACE,iCAAiC,SAAS,KAAK;AAAA,MAEjD;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,WAAW,OAAO;AAAA,MACrC,GAAG;AAAA,MACH,WAAW;AAAA,MACX,cAAc,SAAS;AAAA,IACzB,CAAC;AACD,YAAQ,KAAK,MAAM;AACnB,UAAM,OAAO,MAAM,OAAO,SAAS;AACnC,UAAM,eAAe,oBAAoB,MAAM,UAAU,YAAY;AAIrE,QAAI,cAAc;AAClB,QACE,SAAS,qBACT,aAAa,SAAS,iBAAiB,MAAM,aAAa,YAAY,GACtE;AACA,oBAAc,MAAM,WAAW,OAAO;AAAA,QACpC,GAAG;AAAA,QACH,WAAW,SAAS;AAAA,QACpB,cAAc,SAAS,iBAAiB;AAAA,UACtC;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AACD,cAAQ,KAAK,WAAW;AAAA,IAC1B;AAEA,UAAM,YAAY,IAAI,mBAAmB;AAAA,MACvC,SAAS,EAAE,QAAQ,aAAa,aAAa,QAAQ;AAAA,MACrD,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,aAAa,aAAa;AAAA,QAC1B,GAAI,gBAAgB,UAAU,SAAS,cACnC,EAAE,QAAQ,SAAS,YAAY,IAC/B,CAAC;AAAA,MACP;AAAA,MACA,gBAAgB,MAAM;AAAA,MACtB,UAAU,SAAS;AAAA,MACnB,GAAI,SAAS,oBAAoB,SAC7B,EAAE,iBAAiB,SAAS,gBAAgB,IAC5C,CAAC;AAAA,MACL;AAAA,IACF,CAAC;AAED,UAAM,aAAa,IAAI,gBAAgB;AAAA,MACrC,SAAS,KAAK,KAAK,wBAAwB;AAAA,MAC3C,eAAe,SAAS;AAAA,IAC1B,CAAC;AACD,UAAM,QAAQ,cAAc;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,aAAa,QAAQ,sBAAsB,aAAa;AAAA,MACxD,QAAQ,KAAK,cAAc,SAAS;AAAA,IACtC,CAAC;AAED,WAAO;AAAA,MACL,aAAa,MAAM;AAAA,MACnB,gBAAgB,SAAS;AAAA,MACzB,qBAAqB,SAAS;AAAA,MAC9B;AAAA,MACA;AAAA,MACA,aAAa,CAAC,SAAS,iBAAiB,IAAI;AAAA,MAC5C;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,KAAK,EAAE,MAAM,MAAM,MAAS;AAClC,UAAM;AAAA,EACR;AACF;AAEA,SAAS,aAAa,KAAqB;AACzC,SAAO,IAAI,QAAQ,QAAQ,EAAE,EAAE,QAAQ,UAAU,EAAE;AACrD;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toon-protocol/rig",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.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,8 +42,10 @@
|
|
|
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
50
|
"nostr-tools": "^2.20.0"
|
|
49
51
|
},
|
|
@@ -66,7 +68,6 @@
|
|
|
66
68
|
"build": "tsup",
|
|
67
69
|
"test": "vitest run",
|
|
68
70
|
"test:coverage": "vitest run --coverage",
|
|
69
|
-
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
70
|
-
"factory-job-proof": "tsx scripts/factory-job-proof.ts"
|
|
71
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
|
|
71
72
|
}
|
|
72
73
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/objects.ts"],"sourcesContent":["/**\n * Pure git object construction and SHA-1 envelope hashing.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/git-builder.ts` (#223) —\n * the proven seed pipeline builders, now the core of the Git-to-TOON write\n * path. Everything here is pure: no network, no signing, no payments.\n * Upload/publish lives with the Publisher (#226).\n *\n * Git object format: `<type> <size>\\0<content>`. The SHA-1 is computed over\n * the full envelope (header + NUL + content); the `body` (content only) is\n * what gets uploaded to Arweave.\n */\n\nimport { createHash } from 'node:crypto';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** All git object types TOON can carry. */\nexport type GitObjectType = 'blob' | 'tree' | 'commit' | 'tag';\n\nexport interface GitObject {\n /** SHA-1 hex digest computed over full git envelope */\n sha: string;\n /** Full git object (header + null + content) */\n buffer: Buffer;\n /** Body only (content after the null byte) — this is what gets uploaded */\n body: Buffer;\n}\n\n/**\n * Maximum uploadable git object body size: 95KB safety margin under the\n * 100KB free tier (R10-005). Larger objects are a hard error in v1.\n */\nexport const MAX_OBJECT_SIZE = 95 * 1024;\n\n/**\n * Git's universal empty-blob SHA-1: the object for a zero-byte file.\n *\n * It is the ONLY zero-byte object git can produce (trees, commits, and tags\n * are never empty) and it always has this exact constant value. The store\n * rejects a zero-byte kind:5094 blob upload as malformed (F00), so `rig` never\n * uploads this object — it is skipped on push and synthesized locally on\n * clone/fetch (both keyed off an ACTUAL zero-length body, never a heuristic).\n */\nexport const EMPTY_BLOB_SHA = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';\n\n// ---------------------------------------------------------------------------\n// Envelope hashing\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a raw object body in the git envelope (`<type> <size>\\0`) and compute\n * its SHA-1. This is exactly what `git hash-object -t <type>` does.\n */\nexport function hashGitObject(type: GitObjectType, body: Buffer): GitObject {\n const header = Buffer.from(`${type} ${body.length}\\0`);\n const fullObject = Buffer.concat([header, body]);\n const sha = createHash('sha1').update(fullObject).digest('hex');\n return { sha, buffer: fullObject, body };\n}\n\n// ---------------------------------------------------------------------------\n// Git object construction\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a git blob object and compute its SHA-1.\n *\n * Format: blob <size>\\0<content>\n * SHA is over the full envelope; body is content only (for upload).\n */\nexport function createGitBlob(content: string): GitObject {\n return hashGitObject('blob', Buffer.from(content, 'utf-8'));\n}\n\n/**\n * Construct a git tree object from sorted entries.\n *\n * Format: tree <size>\\0<entries>\n * Each entry: <mode> <name>\\0<20-byte-raw-sha1>\n * Entries MUST be sorted by name (byte-wise).\n */\nexport function createGitTree(\n entries: { mode: string; name: string; sha: string }[]\n): GitObject {\n // Git sorts tree entries by raw byte order (NOT locale-aware)\n const sorted = [...entries].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0\n );\n\n const entryBuffers: Buffer[] = [];\n for (const entry of sorted) {\n const modeAndName = Buffer.from(`${entry.mode} ${entry.name}\\0`);\n // Raw 20-byte SHA-1 (NOT hex)\n const rawSha = Buffer.from(entry.sha, 'hex');\n entryBuffers.push(Buffer.concat([modeAndName, rawSha]));\n }\n\n return hashGitObject('tree', Buffer.concat(entryBuffers));\n}\n\n/**\n * Construct a git commit object.\n *\n * Format: commit <size>\\0tree <tree-sha>\\n[parent ...]\\nauthor ...\\ncommitter ...\\n\\n<message>\n * Tree/parent SHAs are hex-encoded (40 chars) in commits, unlike tree entries.\n */\nexport function createGitCommit(opts: {\n treeSha: string;\n parentSha?: string;\n authorName: string;\n authorPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `tree ${opts.treeSha}`,\n ...(opts.parentSha ? [`parent ${opts.parentSha}`] : []),\n `author ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n `committer ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('commit', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n\n/**\n * Construct an annotated git tag object.\n *\n * Format: tag <size>\\0object <sha>\\ntype <type>\\ntag <name>\\ntagger ...\\n\\n<message>\n * The tagged object is usually a commit, but git allows tagging any object\n * type (including another tag).\n */\nexport function createGitTag(opts: {\n /** SHA-1 of the object being tagged (hex, 40 chars) */\n objectSha: string;\n /** Type of the tagged object (usually 'commit') */\n objectType: GitObjectType;\n /** Tag name, e.g. 'v1.0.0' */\n tagName: string;\n taggerName: string;\n taggerPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `object ${opts.objectSha}`,\n `type ${opts.objectType}`,\n `tag ${opts.tagName}`,\n `tagger ${opts.taggerName} <${opts.taggerPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('tag', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n"],"mappings":";AAaA,SAAS,kBAAkB;AAsBpB,IAAM,kBAAkB,KAAK;AAW7B,IAAM,iBAAiB;AAUvB,SAAS,cAAc,MAAqB,MAAyB;AAC1E,QAAM,SAAS,OAAO,KAAK,GAAG,IAAI,IAAI,KAAK,MAAM,IAAI;AACrD,QAAM,aAAa,OAAO,OAAO,CAAC,QAAQ,IAAI,CAAC;AAC/C,QAAM,MAAM,WAAW,MAAM,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK;AAC9D,SAAO,EAAE,KAAK,QAAQ,YAAY,KAAK;AACzC;AAYO,SAAS,cAAc,SAA4B;AACxD,SAAO,cAAc,QAAQ,OAAO,KAAK,SAAS,OAAO,CAAC;AAC5D;AASO,SAAS,cACd,SACW;AAEX,QAAM,SAAS,CAAC,GAAG,OAAO,EAAE;AAAA,IAAK,CAAC,GAAG,MACnC,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI;AAAA,EAC/C;AAEA,QAAM,eAAyB,CAAC;AAChC,aAAW,SAAS,QAAQ;AAC1B,UAAM,cAAc,OAAO,KAAK,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI;AAE/D,UAAM,SAAS,OAAO,KAAK,MAAM,KAAK,KAAK;AAC3C,iBAAa,KAAK,OAAO,OAAO,CAAC,aAAa,MAAM,CAAC,CAAC;AAAA,EACxD;AAEA,SAAO,cAAc,QAAQ,OAAO,OAAO,YAAY,CAAC;AAC1D;AAQO,SAAS,gBAAgB,MAOlB;AACZ,QAAM,QAAQ;AAAA,IACZ,QAAQ,KAAK,OAAO;AAAA,IACpB,GAAI,KAAK,YAAY,CAAC,UAAU,KAAK,SAAS,EAAE,IAAI,CAAC;AAAA,IACrD,UAAU,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IACxE,aAAa,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IAC3E;AAAA,IACA,KAAK;AAAA,EACP;AACA,SAAO,cAAc,UAAU,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;AACvE;AASO,SAAS,aAAa,MAWf;AACZ,QAAM,QAAQ;AAAA,IACZ,UAAU,KAAK,SAAS;AAAA,IACxB,QAAQ,KAAK,UAAU;AAAA,IACvB,OAAO,KAAK,OAAO;AAAA,IACnB,UAAU,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IACxE;AAAA,IACA,KAAK;AAAA,EACP;AACA,SAAO,cAAc,OAAO,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;AACpE;","names":[]}
|