@fased/fased 0.1.68 → 0.1.69
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/CHANGELOG.md +15 -0
- package/README.md +20 -22
- package/config/sat-runtime.env +6 -2
- package/config/signer-protocol-v2.json +6 -0
- package/dist/agents/tools/wallet-action-tool.js +2 -0
- package/dist/agents/tools/wallet-tool.js +5 -3
- package/dist/brand.js +1 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/cli/program/register.wallet.js +88 -5
- package/dist/commands/wallet.js +842 -57
- package/dist/config/sat-runtime-ids.js +6 -1
- package/dist/control-ui/assets/{app-DyPTS0oM.js → app-BLFLoFPk.js} +8 -8
- package/dist/control-ui/assets/{federation-DgA9e-Pn.js → federation-SGMAnM_8.js} +2 -2
- package/dist/control-ui/assets/{index-Cu13yYHr.js → index-S88kp5ZQ.js} +3 -3
- package/dist/control-ui/assets/{mining-Dg-3ll0F.js → mining-ClI8RW4u.js} +2 -2
- package/dist/control-ui/assets/{wallet-ByKBcYhm.js → wallet-CRIhDQbW.js} +201 -116
- package/dist/control-ui/index.html +1 -1
- package/dist/control-ui/version.json +1 -1
- package/dist/gateway/server-http.js +46 -9
- package/dist/mining/mainnet-sync.js +67 -5
- package/dist/operator/operator-readiness.js +1 -1
- package/dist/plugin-sdk/{audio-preflight-_FVQjR1r.js → audio-preflight-CS7Q_7-R.js} +2 -2
- package/dist/plugin-sdk/channel-plugin-common.d.ts +2 -2
- package/dist/plugin-sdk/{chunk-items-LH4uKSnh.js → chunk-items-BLBZNORg.js} +1 -1
- package/dist/plugin-sdk/command-status.js +2 -2
- package/dist/plugin-sdk/{config-schema-DjU_uD-6.js → config-schema-BiBOUW89.js} +255 -47
- package/dist/plugin-sdk/device-pair.d.ts +1 -1
- package/dist/plugin-sdk/discord.d.ts +4 -4
- package/dist/plugin-sdk/index.d.ts +16 -16
- package/dist/plugin-sdk/index.js +2 -2
- package/dist/plugin-sdk/{monitor-DUMMbG4h.js → monitor-WXdn4mR-.js} +4 -4
- package/dist/plugin-sdk/{monitor-DXIM16Hj.js → monitor-xzVAPbg2.js} +6 -6
- package/dist/plugin-sdk/{openresponses-http-C8lKwta1.js → openresponses-http-DnI1LFgq.js} +2 -2
- package/dist/plugin-sdk/{pairing-message-mKO92L-M.d.ts → pairing-message-CiTcdD6m.d.ts} +1 -1
- package/dist/plugin-sdk/provider-web-search-config-contract.d.ts +1 -1
- package/dist/plugin-sdk/{pw-ai-DJ_gTc4M.js → pw-ai-DZYLDC-S.js} +1 -1
- package/dist/plugin-sdk/{runtime-dependency-nup8niQL.js → runtime-dependency-DczkvPxn.js} +9 -9
- package/dist/plugin-sdk/sat-runtime.d.ts +89 -10
- package/dist/plugin-sdk/sat-runtime.js +2 -2
- package/dist/plugin-sdk/{slack-B-nOOLM0.js → slack-CkIRVDUK.js} +4 -4
- package/dist/plugin-sdk/slack.d.ts +4 -4
- package/dist/plugin-sdk/{sticker-cache-BLiId8Em.js → sticker-cache-Dpap5ce_.js} +2 -2
- package/dist/plugin-sdk/{telegram-actions-1X9A32Dn.js → telegram-actions-Cewdga8H.js} +3 -3
- package/dist/plugin-sdk/telegram.d.ts +4 -4
- package/dist/plugin-sdk/{tmp-fased-dir-BkpWlGUe.d.ts → tmp-fased-dir-BSEHc8MQ.d.ts} +2 -2
- package/dist/plugin-sdk/{types-DtCOO_hW.d.ts → types-BakzIzGN.d.ts} +1 -1
- package/dist/plugin-sdk/{types-B-zL9gSv.d.ts → types-CzF0YQqK.d.ts} +2 -2
- package/dist/plugin-sdk/{web-BvjNyYBz.js → web-DGWi7i3B.js} +2 -2
- package/dist/plugin-sdk/whatsapp.d.ts +9 -9
- package/dist/plugin-sdk/{zod-schema.providers-core-BCDlgtls.d.ts → zod-schema.providers-core-CIzCjXH9.d.ts} +63 -63
- package/dist/wallet/local-socket-signer-lifecycle.js +79 -3
- package/dist/wallet/local-socket-signer-protocol.js +70 -10
- package/dist/wallet/mining-wallet-retirement.js +256 -0
- package/dist/wallet/providers/local-socket-signer-adapter.js +2 -0
- package/dist/wallet/signer-protocol-v2.generated.js +6 -0
- package/dist/wallet/wallet-agent-selection.js +6 -10
- package/dist/wallet/wallet-provider-registry.js +70 -29
- package/dist/wallet/wallet-send-approvals.js +6 -2
- package/dist/wallet/wallet-status.js +56 -1
- package/dist/wizard/onboarding.js +32 -30
- package/dist/wizard/onboarding.wallet.js +10 -1
- package/extensions/acpx/package.json +1 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/fased-federation/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +6 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +2 -2
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +6 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +6 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/sat-mining/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/synology-chat/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +6 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +6 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +6 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +6 -0
- package/extensions/zalouser/package.json +1 -1
- package/install.sh +276 -103
- package/package.json +4 -3
- package/scripts/install-fased-signerd.sh +4 -8
- package/scripts/install-runtime-profile.sh +27 -0
- package/scripts/start-managed.sh +13 -1
- package/scripts/fased-signer-wallet-import-hosting.sh +0 -78
|
@@ -10,7 +10,7 @@ function lockedLocalSignerPolicy(role) {
|
|
|
10
10
|
assets: []
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
async function requireSignerOwnedProtocolV2(socketPath) {
|
|
13
|
+
async function requireSignerOwnedProtocolV2(socketPath, requiredFeatures = []) {
|
|
14
14
|
const result = await callLocalSocketSigner(socketPath, { op: "v2.capabilities" });
|
|
15
15
|
const capabilities = result.capabilities;
|
|
16
16
|
const required = [
|
|
@@ -19,13 +19,89 @@ async function requireSignerOwnedProtocolV2(socketPath) {
|
|
|
19
19
|
"signerOwnedKeys",
|
|
20
20
|
"applicationNetworkBootstrap",
|
|
21
21
|
"atomicMultiAssetCaps",
|
|
22
|
-
"signerControlledNativeFeeCaps"
|
|
22
|
+
"signerControlledNativeFeeCaps",
|
|
23
|
+
...requiredFeatures
|
|
23
24
|
];
|
|
24
25
|
const missing = required.filter((feature) => !capabilities?.features.includes(feature));
|
|
25
26
|
if (result.ready !== true || capabilities?.protocol.current !== 2 || capabilities.protocol.min > 2 || capabilities.protocol.max < 2 || capabilities.nativeFeeReservationLamports !== LOCAL_SIGNER_NATIVE_FEE_RESERVATION_LAMPORTS_V2 || missing.length > 0) {
|
|
26
27
|
throw new Error(`fased-signerd does not provide the required signer-owned protocol-v2 capabilities${missing.length > 0 ? `: ${missing.join(", ")}` : ""}`);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
function assertRoleBaselineRecord(record, role) {
|
|
31
|
+
if (record.policy.role !== role) {
|
|
32
|
+
throw new Error(`signer-owned wallet ${record.wallet.walletId} has role=${record.policy.role}, not ${role}`);
|
|
33
|
+
}
|
|
34
|
+
if (record.policy.baselineVersion !== 1 || record.policy.operations.length === 0 || record.policy.programs.length === 0 || record.policy.assets.length === 0) {
|
|
35
|
+
throw new Error(`signer-owned wallet ${record.wallet.walletId} is not role-ready; select Activate role baseline before using it`);
|
|
36
|
+
}
|
|
37
|
+
return record;
|
|
38
|
+
}
|
|
39
|
+
async function createRoleReadySignerOwnedWallet(params) {
|
|
40
|
+
const walletId = params.walletId.trim();
|
|
41
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(walletId)) {
|
|
42
|
+
throw new Error("walletId must contain only letters, numbers, hyphens, or underscores");
|
|
43
|
+
}
|
|
44
|
+
await requireSignerOwnedProtocolV2(params.socketPath, ["signerOwnedRoleBaselines"]);
|
|
45
|
+
try {
|
|
46
|
+
const existing = await readSignerOwnedWallet({
|
|
47
|
+
socketPath: params.socketPath,
|
|
48
|
+
walletId
|
|
49
|
+
});
|
|
50
|
+
if (!params.allowExisting) {
|
|
51
|
+
throw new Error(`signer-owned wallet already exists: ${walletId}`);
|
|
52
|
+
}
|
|
53
|
+
return assertRoleBaselineRecord(existing, params.role);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
if (message.startsWith("signer-owned wallet")) {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const created = await callLocalSocketSigner(params.socketPath, {
|
|
62
|
+
op: "v2.wallet.create",
|
|
63
|
+
walletId,
|
|
64
|
+
request: {
|
|
65
|
+
expectedPolicyVersion: 0,
|
|
66
|
+
baseline: {
|
|
67
|
+
version: 1,
|
|
68
|
+
role: params.role
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return assertRoleBaselineRecord(created, params.role);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (!params.allowExisting) {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
const existing = await readSignerOwnedWallet({
|
|
78
|
+
socketPath: params.socketPath,
|
|
79
|
+
walletId
|
|
80
|
+
});
|
|
81
|
+
return assertRoleBaselineRecord(existing, params.role);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function activateSignerOwnedRoleBaseline(params) {
|
|
85
|
+
await requireSignerOwnedProtocolV2(params.socketPath, ["signerOwnedRoleBaselines"]);
|
|
86
|
+
return await callLocalSocketSigner(params.socketPath, {
|
|
87
|
+
op: "v2.policy.activateBaseline",
|
|
88
|
+
walletId: params.walletId,
|
|
89
|
+
request: {
|
|
90
|
+
expectedPolicyVersion: params.expectedPolicyVersion,
|
|
91
|
+
baseline: {
|
|
92
|
+
version: 1,
|
|
93
|
+
role: params.role
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function readSignerOwnedWalletReadiness(params) {
|
|
99
|
+
await requireSignerOwnedProtocolV2(params.socketPath, ["liveWalletReadiness"]);
|
|
100
|
+
return await callLocalSocketSigner(params.socketPath, {
|
|
101
|
+
op: "v2.wallet.readiness",
|
|
102
|
+
walletId: params.walletId
|
|
103
|
+
});
|
|
104
|
+
}
|
|
29
105
|
async function readSignerOwnedWallet(params) {
|
|
30
106
|
const [wallet, policy] = await Promise.all([callLocalSocketSigner(params.socketPath, {
|
|
31
107
|
op: "v2.wallet.get",
|
|
@@ -122,4 +198,4 @@ async function configureSignerOwnedWalletPrimaryRpc(params) {
|
|
|
122
198
|
}
|
|
123
199
|
|
|
124
200
|
//#endregion
|
|
125
|
-
export { configureSignerOwnedWalletPrimaryRpc,
|
|
201
|
+
export { activateSignerOwnedRoleBaseline, configureSignerOwnedWalletPrimaryRpc, createRoleReadySignerOwnedWallet, readSignerOwnedWallet, readSignerOwnedWalletReadiness };
|
|
@@ -36,14 +36,18 @@ const SignerPolicyAssetV2Schema = Type.Object({
|
|
|
36
36
|
asset: Type.String(),
|
|
37
37
|
destinations: Type.Array(Type.String()),
|
|
38
38
|
maxPerTx: Type.String(),
|
|
39
|
-
maxDaily: Type.String()
|
|
39
|
+
maxDaily: Type.String(),
|
|
40
|
+
reviewedDestinations: Type.Optional(Type.Boolean()),
|
|
41
|
+
typedSatDestinations: Type.Optional(Type.Boolean())
|
|
40
42
|
}, { additionalProperties: false });
|
|
41
43
|
const SignerPolicyInputV2Schema = Type.Object({
|
|
42
44
|
walletId: Type.Optional(Type.String()),
|
|
43
45
|
role: SignerWalletRoleSchema,
|
|
44
46
|
version: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
47
|
+
baselineVersion: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
45
48
|
operations: Type.Array(Type.String()),
|
|
46
49
|
programs: Type.Array(Type.String()),
|
|
50
|
+
typedSatPrograms: Type.Optional(Type.Boolean()),
|
|
47
51
|
assets: Type.Array(SignerPolicyAssetV2Schema),
|
|
48
52
|
hash: Type.Optional(Type.String())
|
|
49
53
|
}, { additionalProperties: false });
|
|
@@ -51,8 +55,10 @@ const LocalSocketSignerPolicyV2Schema = Type.Object({
|
|
|
51
55
|
walletId: Type.String(),
|
|
52
56
|
role: SignerWalletRoleSchema,
|
|
53
57
|
version: Type.Integer({ minimum: 1 }),
|
|
58
|
+
baselineVersion: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
54
59
|
operations: Type.Array(Type.String()),
|
|
55
60
|
programs: Type.Array(Type.String()),
|
|
61
|
+
typedSatPrograms: Type.Optional(Type.Boolean()),
|
|
56
62
|
assets: Type.Array(SignerPolicyAssetV2Schema),
|
|
57
63
|
hash: Type.String({ pattern: "^sha256:[0-9a-f]{64}$" })
|
|
58
64
|
}, { additionalProperties: false });
|
|
@@ -238,10 +244,13 @@ const SignerSolanaTransactionEnvelopeV2Schema = Type.Object({
|
|
|
238
244
|
}),
|
|
239
245
|
submission: Type.Literal("rpc")
|
|
240
246
|
}, { additionalProperties: false });
|
|
241
|
-
const SignerReviewAuthorizationV2Schema = Type.Object({
|
|
247
|
+
const SignerReviewAuthorizationV2Schema = Type.Union([Type.Object({
|
|
242
248
|
type: Type.Literal("webauthn"),
|
|
243
249
|
proof: Type.Object({ proofId: Type.String({ minLength: 1 }) }, { additionalProperties: false })
|
|
244
|
-
}, { additionalProperties: false })
|
|
250
|
+
}, { additionalProperties: false }), Type.Object({
|
|
251
|
+
type: Type.Literal("control-ui"),
|
|
252
|
+
proof: Type.Object({ proofId: Type.String({ pattern: "^[0-9a-f]{64}$" }) }, { additionalProperties: false })
|
|
253
|
+
}, { additionalProperties: false })]);
|
|
245
254
|
const LocalSocketSignerJupiterTriggerHistoryV2Schema = Type.Object({ orders: Type.Array(Type.Object({
|
|
246
255
|
orderId: Type.String({ minLength: 1 }),
|
|
247
256
|
orderState: Type.String({ minLength: 1 }),
|
|
@@ -266,10 +275,17 @@ const LocalSocketSignerJupiterTriggerHistoryV2Schema = Type.Object({ orders: Typ
|
|
|
266
275
|
program: Type.String({ minLength: 1 })
|
|
267
276
|
}, { additionalProperties: false }))
|
|
268
277
|
}, { additionalProperties: false })) }, { additionalProperties: false });
|
|
269
|
-
const
|
|
278
|
+
const SignerRoleBaselineV1Schema = Type.Object({
|
|
279
|
+
version: Type.Literal(1),
|
|
280
|
+
role: SignerWalletRoleSchema
|
|
281
|
+
}, { additionalProperties: false });
|
|
282
|
+
const SignerWalletPolicyCreateV2Schema = Type.Union([Type.Object({
|
|
270
283
|
expectedPolicyVersion: Type.Literal(0),
|
|
271
284
|
policy: SignerPolicyInputV2Schema
|
|
272
|
-
}, { additionalProperties: false })
|
|
285
|
+
}, { additionalProperties: false }), Type.Object({
|
|
286
|
+
expectedPolicyVersion: Type.Literal(0),
|
|
287
|
+
baseline: SignerRoleBaselineV1Schema
|
|
288
|
+
}, { additionalProperties: false })]);
|
|
273
289
|
const SignerOperationLookupV2Schema = Type.Object({ requestId: Type.String() }, { additionalProperties: false });
|
|
274
290
|
const SignerSatLookupBindingRequestV2Schema = Type.Object({
|
|
275
291
|
cycleId: Type.String({ pattern: "^(0|[1-9][0-9]*)$" }),
|
|
@@ -348,6 +364,18 @@ const LocalSocketSignerRequestSchema = Type.Union([
|
|
|
348
364
|
op: Type.Literal("v2.wallet.get"),
|
|
349
365
|
walletId: Type.String()
|
|
350
366
|
}, { additionalProperties: false }),
|
|
367
|
+
Type.Object({
|
|
368
|
+
op: Type.Literal("v2.wallet.readiness"),
|
|
369
|
+
walletId: Type.String()
|
|
370
|
+
}, { additionalProperties: false }),
|
|
371
|
+
Type.Object({
|
|
372
|
+
op: Type.Literal("v2.policy.activateBaseline"),
|
|
373
|
+
walletId: Type.String(),
|
|
374
|
+
request: Type.Object({
|
|
375
|
+
expectedPolicyVersion: Type.Integer({ minimum: 1 }),
|
|
376
|
+
baseline: SignerRoleBaselineV1Schema
|
|
377
|
+
}, { additionalProperties: false })
|
|
378
|
+
}, { additionalProperties: false }),
|
|
351
379
|
Type.Object({
|
|
352
380
|
op: Type.Literal("v2.wallet.create"),
|
|
353
381
|
walletId: Type.String(),
|
|
@@ -356,21 +384,30 @@ const LocalSocketSignerRequestSchema = Type.Union([
|
|
|
356
384
|
Type.Object({
|
|
357
385
|
op: Type.Literal("v2.wallet.import"),
|
|
358
386
|
walletId: Type.String(),
|
|
359
|
-
request: Type.Object({
|
|
387
|
+
request: Type.Union([Type.Object({
|
|
360
388
|
expectedPolicyVersion: Type.Literal(0),
|
|
361
389
|
policy: SignerPolicyInputV2Schema,
|
|
362
390
|
path: Type.String()
|
|
363
|
-
}, { additionalProperties: false })
|
|
391
|
+
}, { additionalProperties: false }), Type.Object({
|
|
392
|
+
expectedPolicyVersion: Type.Literal(0),
|
|
393
|
+
baseline: SignerRoleBaselineV1Schema,
|
|
394
|
+
path: Type.String()
|
|
395
|
+
}, { additionalProperties: false })])
|
|
364
396
|
}, { additionalProperties: false }),
|
|
365
397
|
Type.Object({
|
|
366
398
|
op: Type.Literal("v2.wallet.importLegacy"),
|
|
367
399
|
walletId: Type.String(),
|
|
368
|
-
request: Type.Object({
|
|
400
|
+
request: Type.Union([Type.Object({
|
|
369
401
|
expectedPolicyVersion: Type.Literal(0),
|
|
370
402
|
policy: SignerPolicyInputV2Schema,
|
|
371
403
|
path: Type.String(),
|
|
372
404
|
passphrasePath: Type.String()
|
|
373
|
-
}, { additionalProperties: false })
|
|
405
|
+
}, { additionalProperties: false }), Type.Object({
|
|
406
|
+
expectedPolicyVersion: Type.Literal(0),
|
|
407
|
+
baseline: SignerRoleBaselineV1Schema,
|
|
408
|
+
path: Type.String(),
|
|
409
|
+
passphrasePath: Type.String()
|
|
410
|
+
}, { additionalProperties: false })])
|
|
374
411
|
}, { additionalProperties: false }),
|
|
375
412
|
Type.Object({
|
|
376
413
|
op: Type.Literal("v2.wallet.reencrypt"),
|
|
@@ -650,6 +687,27 @@ const LocalSocketSignerWalletPolicyResultV2Schema = Type.Object({
|
|
|
650
687
|
wallet: LocalSocketSignerWalletV2Schema,
|
|
651
688
|
policy: LocalSocketSignerPolicyV2Schema
|
|
652
689
|
}, { additionalProperties: false });
|
|
690
|
+
const LocalSocketSignerWalletReadinessV2Schema = Type.Object({
|
|
691
|
+
walletId: Type.String({ minLength: 1 }),
|
|
692
|
+
publicKey: Type.String({ minLength: 1 }),
|
|
693
|
+
walletVersion: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
694
|
+
role: SignerWalletRoleSchema,
|
|
695
|
+
baselineVersion: Type.Integer({ minimum: 0 }),
|
|
696
|
+
policyVersion: Type.Integer({ minimum: 1 }),
|
|
697
|
+
policyHash: Type.String({ pattern: "^sha256:[0-9a-f]{64}$" }),
|
|
698
|
+
networkVersion: Type.Integer({ minimum: 0 }),
|
|
699
|
+
networkHash: Type.Optional(Type.String({ pattern: "^hmac-sha256:[0-9a-f]{64}$" })),
|
|
700
|
+
keyReady: Type.Boolean(),
|
|
701
|
+
policyReady: Type.Boolean(),
|
|
702
|
+
networkReady: Type.Boolean(),
|
|
703
|
+
operationLane: Type.Union([
|
|
704
|
+
Type.Literal("blocked"),
|
|
705
|
+
Type.Literal("agent-reviewed-and-autonomous"),
|
|
706
|
+
Type.Literal("mining-typed-sat"),
|
|
707
|
+
Type.Literal("vault-reviewed-only")
|
|
708
|
+
]),
|
|
709
|
+
ready: Type.Boolean()
|
|
710
|
+
}, { additionalProperties: false });
|
|
653
711
|
function isPositiveUnsignedInteger(value) {
|
|
654
712
|
return typeof value === "string" && /^[1-9][0-9]*$/.test(value);
|
|
655
713
|
}
|
|
@@ -694,9 +752,11 @@ function validateLocalSocketSignerResult(op, result) {
|
|
|
694
752
|
case "v2.network.bootstrap": return Value.Check(LocalSocketSignerNetworkSummaryV2Schema, result);
|
|
695
753
|
case "v2.policy.get":
|
|
696
754
|
case "v2.policy.put":
|
|
697
|
-
case "v2.policy.tighten":
|
|
755
|
+
case "v2.policy.tighten":
|
|
756
|
+
case "v2.policy.activateBaseline": return Value.Check(LocalSocketSignerPolicyV2Schema, result);
|
|
698
757
|
case "v2.wallet.get":
|
|
699
758
|
case "v2.wallet.reencrypt": return Value.Check(LocalSocketSignerWalletV2Schema, result);
|
|
759
|
+
case "v2.wallet.readiness": return Value.Check(LocalSocketSignerWalletReadinessV2Schema, result);
|
|
700
760
|
case "v2.wallet.create":
|
|
701
761
|
case "v2.wallet.import":
|
|
702
762
|
case "v2.wallet.importLegacy": return Value.Check(LocalSocketSignerWalletPolicyResultV2Schema, result);
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { resolveStateDir } from "../config/paths.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
+
|
|
6
|
+
//#region src/wallet/mining-wallet-retirement.ts
|
|
7
|
+
function record(value) {
|
|
8
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
9
|
+
}
|
|
10
|
+
function decimal(value, field) {
|
|
11
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
12
|
+
if (!/^\d+$/u.test(normalized)) {
|
|
13
|
+
throw new Error(`Mining retirement cannot verify ${field}`);
|
|
14
|
+
}
|
|
15
|
+
return normalized;
|
|
16
|
+
}
|
|
17
|
+
function nonNegativeCount(value) {
|
|
18
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
19
|
+
}
|
|
20
|
+
function sha256(raw) {
|
|
21
|
+
return `sha256:${createHash("sha256").update(raw).digest("hex")}`;
|
|
22
|
+
}
|
|
23
|
+
function base64UrlByteLength(value) {
|
|
24
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/u.test(value)) {
|
|
25
|
+
return -1;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return Buffer.from(value, "base64url").byteLength;
|
|
29
|
+
} catch {
|
|
30
|
+
return -1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function normalizedWalletStateKey(walletId) {
|
|
34
|
+
return walletId.replace(/[^a-zA-Z0-9._-]+/gu, "_") || "unattached";
|
|
35
|
+
}
|
|
36
|
+
function readStrictJSONFile(filePath, maxBytes) {
|
|
37
|
+
const info = fs.lstatSync(filePath);
|
|
38
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size <= 0 || info.size > maxBytes) {
|
|
39
|
+
throw new Error(`Mining retirement evidence file is invalid: ${filePath}`);
|
|
40
|
+
}
|
|
41
|
+
const raw = fs.readFileSync(filePath);
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(raw.toString("utf8"));
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error(`Mining retirement evidence file is not valid JSON: ${filePath}`);
|
|
47
|
+
}
|
|
48
|
+
const value = record(parsed);
|
|
49
|
+
if (!value) {
|
|
50
|
+
throw new Error(`Mining retirement evidence file must contain one JSON object: ${filePath}`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
raw,
|
|
54
|
+
value
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function unwrapMiningGatewayPayload(value) {
|
|
58
|
+
let current = value;
|
|
59
|
+
for (let index = 0; index < 4; index += 1) {
|
|
60
|
+
const currentRecord = record(current);
|
|
61
|
+
if (!currentRecord) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (record(currentRecord.status)) {
|
|
65
|
+
return record(currentRecord.status);
|
|
66
|
+
}
|
|
67
|
+
if (!("payload" in currentRecord)) {
|
|
68
|
+
return currentRecord;
|
|
69
|
+
}
|
|
70
|
+
current = currentRecord.payload;
|
|
71
|
+
}
|
|
72
|
+
return record(current) ?? {};
|
|
73
|
+
}
|
|
74
|
+
function verifyMiningRecoveryPackage(params) {
|
|
75
|
+
const recoveryFile = params.recoveryFile.trim();
|
|
76
|
+
if (!path.isAbsolute(recoveryFile) || path.resolve(recoveryFile) !== recoveryFile) {
|
|
77
|
+
throw new Error("--recovery-file must be an absolute clean path");
|
|
78
|
+
}
|
|
79
|
+
const info = fs.lstatSync(recoveryFile);
|
|
80
|
+
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.mode & 63) {
|
|
81
|
+
throw new Error("Mining recovery package must be one owner-only regular non-symlink file");
|
|
82
|
+
}
|
|
83
|
+
if (typeof process.getuid === "function" && info.uid !== process.getuid()) {
|
|
84
|
+
throw new Error("Mining recovery package must be owned by the current terminal user");
|
|
85
|
+
}
|
|
86
|
+
const { raw, value } = readStrictJSONFile(recoveryFile, 16 * 1024);
|
|
87
|
+
const kdf = record(value.kdf);
|
|
88
|
+
const encryption = record(value.encryption);
|
|
89
|
+
if (value.kind !== "fased-signer-wallet-recovery" || value.version !== 1 || value.walletId !== params.walletId || value.role !== "mining" || value.publicKey !== params.publicKey || typeof value.createdAt !== "string" || !Number.isFinite(Date.parse(value.createdAt)) || kdf?.name !== "argon2id" || kdf.memoryKiB !== 64 * 1024 || kdf.iterations !== 3 || kdf.parallelism !== 1 || base64UrlByteLength(kdf.salt) !== 16 || encryption?.name !== "aes-256-gcm" || base64UrlByteLength(encryption.nonce) !== 12 || base64UrlByteLength(encryption.ciphertext) !== 80) {
|
|
90
|
+
throw new Error("Encrypted recovery package does not match the source Mining wallet");
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
packageHash: sha256(raw),
|
|
94
|
+
walletId: params.walletId,
|
|
95
|
+
publicKey: params.publicKey,
|
|
96
|
+
role: "mining"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function buildMiningRetirementEvidence(params) {
|
|
100
|
+
const env = params.env ?? process.env;
|
|
101
|
+
const status = unwrapMiningGatewayPayload(params.liveStatus);
|
|
102
|
+
if (typeof status.retirementGatewayError === "string") {
|
|
103
|
+
throw new Error(`Could not stop and inspect Mining: ${status.retirementGatewayError}`);
|
|
104
|
+
}
|
|
105
|
+
if (status.walletId !== params.walletId && status.walletId !== params.signerWalletId) {
|
|
106
|
+
throw new Error("Live Mining status is not bound to the source wallet");
|
|
107
|
+
}
|
|
108
|
+
if (status.statusFresh === false || status.degraded === true) {
|
|
109
|
+
throw new Error("Live Mining status is degraded; normal retirement refuses unverifiable state");
|
|
110
|
+
}
|
|
111
|
+
if (status.running === true || status.drainOnly === true || status.enabledWanted === true) {
|
|
112
|
+
throw new Error("Mining is still running or draining; retry retirement after Clearing finishes");
|
|
113
|
+
}
|
|
114
|
+
const workers = record(status.workers) ?? {};
|
|
115
|
+
if (Object.values(workers).some((worker) => record(worker)?.running === true)) {
|
|
116
|
+
throw new Error("A Mining worker is still running");
|
|
117
|
+
}
|
|
118
|
+
const solBalanceLamports = decimal(status.currentSolBalanceLamports, "live SOL balance");
|
|
119
|
+
const signerBalance = decimal(params.signerSolBalanceLamports, "signer SOL balance");
|
|
120
|
+
if (solBalanceLamports !== signerBalance) {
|
|
121
|
+
throw new Error("Live Mining and signer SOL balance observations disagree");
|
|
122
|
+
}
|
|
123
|
+
const satBalanceRaw = decimal(status.currentSatBalanceRaw, "live SAT balance");
|
|
124
|
+
for (const [field, value] of [
|
|
125
|
+
["funded Mining capital", status.currentCapitalFundedLamports],
|
|
126
|
+
["locked Mining capital", status.currentCapitalLockedLamports],
|
|
127
|
+
["free Mining capital", status.currentCapitalFreeLamports]
|
|
128
|
+
]) {
|
|
129
|
+
if (decimal(value, field) !== "0") {
|
|
130
|
+
throw new Error(`${field} must be zero before normal Mining retirement`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (nonNegativeCount(status.currentCapitalPendingCycleCount) !== 0 || nonNegativeCount(record(status.claimBacklog)?.total) !== 0 || nonNegativeCount(status.missingCycleCount) !== 0 || Array.isArray(status.pendingCycleIds) && status.pendingCycleIds.length > 0 || status.exactPendingCycleId != null) {
|
|
134
|
+
throw new Error("Mining still has pending cycles, claims, or reconciliation gaps");
|
|
135
|
+
}
|
|
136
|
+
const walletStateDir = path.join(resolveStateDir(env), "sat-mining", "wallets", normalizedWalletStateKey(params.walletId));
|
|
137
|
+
const runtime = readStrictJSONFile(path.join(walletStateDir, "runtime-store.json"), 16 * 1024 * 1024);
|
|
138
|
+
if (runtime.value.enabledWanted === true) {
|
|
139
|
+
throw new Error("Durable Mining state still permits new jobs");
|
|
140
|
+
}
|
|
141
|
+
const durableWorkers = record(runtime.value.workers) ?? {};
|
|
142
|
+
if (Object.values(durableWorkers).some((worker) => record(worker)?.running === true)) {
|
|
143
|
+
throw new Error("Durable Mining state still records a running worker");
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(runtime.value.pendingPlannerCycles) && runtime.value.pendingPlannerCycles.length) {
|
|
146
|
+
throw new Error("Durable Mining planner work remains pending");
|
|
147
|
+
}
|
|
148
|
+
if (Array.isArray(runtime.value.claimBacklog) && runtime.value.claimBacklog.length) {
|
|
149
|
+
throw new Error("Durable Mining claim backlog remains pending");
|
|
150
|
+
}
|
|
151
|
+
const lastKnown = record(runtime.value.lastKnownStatus);
|
|
152
|
+
if (!lastKnown || lastKnown.walletId !== params.walletId && lastKnown.walletId !== params.signerWalletId) {
|
|
153
|
+
throw new Error("Durable Mining balance state is missing or belongs to another wallet");
|
|
154
|
+
}
|
|
155
|
+
if (decimal(lastKnown.currentCapitalFundedLamports, "durable funded capital") !== "0" || decimal(lastKnown.currentCapitalLockedLamports, "durable locked capital") !== "0" || decimal(lastKnown.currentCapitalFreeLamports, "durable free capital") !== "0" || nonNegativeCount(lastKnown.currentCapitalPendingCycleCount) !== 0 || lastKnown.exactPendingCycleId != null) {
|
|
156
|
+
throw new Error("Durable Mining state still contains pending capital or protocol work");
|
|
157
|
+
}
|
|
158
|
+
const ledgerPath = path.join(walletStateDir, "submission-ledger.json");
|
|
159
|
+
let ledgerRaw = Buffer.from("{\"version\":1,\"records\":{}}\n", "utf8");
|
|
160
|
+
let ledger = {
|
|
161
|
+
version: 1,
|
|
162
|
+
records: {}
|
|
163
|
+
};
|
|
164
|
+
if (fs.existsSync(ledgerPath)) {
|
|
165
|
+
const loaded = readStrictJSONFile(ledgerPath, 16 * 1024 * 1024);
|
|
166
|
+
ledgerRaw = loaded.raw;
|
|
167
|
+
ledger = loaded.value;
|
|
168
|
+
}
|
|
169
|
+
if (ledger.version !== 1 || !record(ledger.records)) {
|
|
170
|
+
throw new Error("Durable Mining submission ledger is invalid");
|
|
171
|
+
}
|
|
172
|
+
const pendingByKind = {
|
|
173
|
+
commit: 0,
|
|
174
|
+
reveal: 0,
|
|
175
|
+
settlement: 0,
|
|
176
|
+
claim: 0,
|
|
177
|
+
cleanup: 0,
|
|
178
|
+
alt: 0
|
|
179
|
+
};
|
|
180
|
+
for (const rawEntry of Object.values(record(ledger.records))) {
|
|
181
|
+
const entry = record(rawEntry);
|
|
182
|
+
if (!entry) {
|
|
183
|
+
throw new Error("Durable Mining submission ledger contains an invalid record");
|
|
184
|
+
}
|
|
185
|
+
const state = typeof entry.state === "string" ? entry.state : "";
|
|
186
|
+
if (![
|
|
187
|
+
"prepared",
|
|
188
|
+
"reserved",
|
|
189
|
+
"broadcast",
|
|
190
|
+
"unknown"
|
|
191
|
+
].includes(state)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const action = typeof entry.action === "string" ? entry.action.toLowerCase() : "";
|
|
195
|
+
if (action.includes("commit")) {
|
|
196
|
+
pendingByKind.commit += 1;
|
|
197
|
+
} else if (action.includes("reveal") || action.includes("submit")) {
|
|
198
|
+
pendingByKind.reveal += 1;
|
|
199
|
+
} else if (action.includes("settle") || action.includes("score") || action.includes("distribute") || action.includes("finalize")) {
|
|
200
|
+
pendingByKind.settlement += 1;
|
|
201
|
+
} else if (action.includes("claim")) {
|
|
202
|
+
pendingByKind.claim += 1;
|
|
203
|
+
} else if (action.includes("lookup") || action.includes("alt")) {
|
|
204
|
+
pendingByKind.alt += 1;
|
|
205
|
+
} else {
|
|
206
|
+
pendingByKind.cleanup += 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (Object.values(pendingByKind).some((count) => count > 0)) {
|
|
210
|
+
throw new Error("Reserved, broadcast, or unknown Mining submissions must be reconciled first");
|
|
211
|
+
}
|
|
212
|
+
const observedAt = typeof status.updatedAt === "string" ? status.updatedAt : typeof status.snapshotAt === "string" ? status.snapshotAt : typeof lastKnown.updatedAt === "string" ? lastKnown.updatedAt : "";
|
|
213
|
+
if (!observedAt || !Number.isFinite(Date.parse(observedAt))) {
|
|
214
|
+
throw new Error("Live Mining balance observation has no verifiable timestamp");
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
version: 1,
|
|
218
|
+
walletId: params.signerWalletId,
|
|
219
|
+
publicKey: params.publicKey,
|
|
220
|
+
observedAt: new Date(observedAt).toISOString(),
|
|
221
|
+
newJobsStopped: true,
|
|
222
|
+
workersDrained: true,
|
|
223
|
+
clearingDrained: true,
|
|
224
|
+
submissionsReconciled: true,
|
|
225
|
+
pendingCommits: pendingByKind.commit,
|
|
226
|
+
pendingReveals: pendingByKind.reveal,
|
|
227
|
+
pendingSettlements: pendingByKind.settlement,
|
|
228
|
+
pendingClaims: pendingByKind.claim,
|
|
229
|
+
pendingCleanup: pendingByKind.cleanup,
|
|
230
|
+
pendingAltMutations: pendingByKind.alt,
|
|
231
|
+
solBalanceLamports,
|
|
232
|
+
satBalanceRaw,
|
|
233
|
+
runtimeStateHash: sha256(runtime.raw),
|
|
234
|
+
submissionLedgerHash: sha256(ledgerRaw)
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function writeMiningRetirementReceipt(params) {
|
|
238
|
+
const root = path.join(resolveStateDir(params.env ?? process.env), "wallet-retirements");
|
|
239
|
+
fs.mkdirSync(root, {
|
|
240
|
+
recursive: true,
|
|
241
|
+
mode: 448
|
|
242
|
+
});
|
|
243
|
+
const filePath = path.join(root, `${normalizedWalletStateKey(params.sourceWalletId)}.json`);
|
|
244
|
+
const tempPath = `${filePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
245
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(params.receipt, null, 2)}\n`, {
|
|
246
|
+
encoding: "utf8",
|
|
247
|
+
flag: "wx",
|
|
248
|
+
mode: 384
|
|
249
|
+
});
|
|
250
|
+
fs.renameSync(tempPath, filePath);
|
|
251
|
+
fs.chmodSync(filePath, 384);
|
|
252
|
+
return filePath;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
export { buildMiningRetirementEvidence, verifyMiningRecoveryPackage, writeMiningRetirementReceipt };
|
|
@@ -20,7 +20,9 @@ const SIGNER_SOCKET_TIMEOUT_MS = {
|
|
|
20
20
|
"v2.policy.get": 5e3,
|
|
21
21
|
"v2.policy.put": 5e3,
|
|
22
22
|
"v2.policy.tighten": 5e3,
|
|
23
|
+
"v2.policy.activateBaseline": 1e4,
|
|
23
24
|
"v2.wallet.get": 5e3,
|
|
25
|
+
"v2.wallet.readiness": 5e3,
|
|
24
26
|
"v2.wallet.create": 1e4,
|
|
25
27
|
"v2.wallet.import": 2e4,
|
|
26
28
|
"v2.wallet.importLegacy": 3e4,
|
|
@@ -35,12 +35,16 @@ const SIGNER_PROTOCOL_V2 = {
|
|
|
35
35
|
"atomicIdempotency",
|
|
36
36
|
"ambiguousBroadcastReconciliation",
|
|
37
37
|
"signerOwnedKeys",
|
|
38
|
+
"signerOwnedRoleBaselines",
|
|
39
|
+
"liveWalletReadiness",
|
|
38
40
|
"signerOwnedSuccessorRotation",
|
|
41
|
+
"signerOwnedMiningRetirementEvidence",
|
|
39
42
|
"permanentRetiredWalletPolicies",
|
|
40
43
|
"signerOwnedRPC",
|
|
41
44
|
"applicationNetworkBootstrap",
|
|
42
45
|
"signerOwnedEncryptedRecovery",
|
|
43
46
|
"signerOwnedRawExport",
|
|
47
|
+
"restrictedOperatorLifecycle",
|
|
44
48
|
"typedSolanaTransactions",
|
|
45
49
|
"typedA2aPaymentMemos",
|
|
46
50
|
"typedA2aRefundMemos",
|
|
@@ -77,6 +81,8 @@ const SIGNER_PROTOCOL_V2_REQUIRED_CLIENT_FEATURES = [
|
|
|
77
81
|
"atomicIdempotency",
|
|
78
82
|
"ambiguousBroadcastReconciliation",
|
|
79
83
|
"signerOwnedKeys",
|
|
84
|
+
"signerOwnedRoleBaselines",
|
|
85
|
+
"liveWalletReadiness",
|
|
80
86
|
"signerOwnedRPC",
|
|
81
87
|
"applicationNetworkBootstrap",
|
|
82
88
|
"typedSolanaTransactions",
|
|
@@ -42,16 +42,12 @@ function resolveWalletActionSelection(params) {
|
|
|
42
42
|
providerId: params.providerId,
|
|
43
43
|
env
|
|
44
44
|
});
|
|
45
|
-
} else if (registry.defaultWalletId) {
|
|
46
|
-
const fallback = registry.wallets.find((entry) => entry.id === registry.defaultWalletId);
|
|
47
|
-
selection = fallback ? {
|
|
48
|
-
walletId: fallback.id,
|
|
49
|
-
walletName: fallback.name,
|
|
50
|
-
providerId: fallback.providerId,
|
|
51
|
-
source: "fallback"
|
|
52
|
-
} : { source: "none" };
|
|
53
45
|
} else {
|
|
54
|
-
selection = {
|
|
46
|
+
selection = resolveWalletSelection({
|
|
47
|
+
agentId: params.agentId,
|
|
48
|
+
skillWalletId: params.skillWalletId,
|
|
49
|
+
env
|
|
50
|
+
});
|
|
55
51
|
}
|
|
56
52
|
} catch (error) {
|
|
57
53
|
const message = error instanceof Error ? error.message : "wallet selection failed";
|
|
@@ -61,7 +57,7 @@ function resolveWalletActionSelection(params) {
|
|
|
61
57
|
throw new WalletActionSelectionError("wallet_not_found", message);
|
|
62
58
|
}
|
|
63
59
|
if (!selection.walletId) {
|
|
64
|
-
throw new WalletActionSelectionError("wallet_handle_required", "Agent wallet
|
|
60
|
+
throw new WalletActionSelectionError("wallet_handle_required", "Select an Agent wallet: use an explicit wallet, skill override, Agent assignment, or Default Agent wallet fallback");
|
|
65
61
|
}
|
|
66
62
|
const wallet = registry.wallets.find((entry) => entry.id === selection.walletId);
|
|
67
63
|
if (!wallet) {
|