@vallum/accounts 0.0.0-prerelease
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 +12 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +139 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @vallum/accounts
|
|
2
|
+
|
|
3
|
+
Signer-reference-first account and wallet primitives for Vallum.
|
|
4
|
+
|
|
5
|
+
This package is intentionally local/mock-first. It lets tests and demos create
|
|
6
|
+
agent wallet accounts while returning wallet addresses and scoped signer
|
|
7
|
+
references instead of seeds, mnemonics, private keys, raw keypairs, raw
|
|
8
|
+
transaction bytes, user signatures, sponsor keys, app API keys, or bearer
|
|
9
|
+
tokens.
|
|
10
|
+
|
|
11
|
+
Production custody, KMS integrations, and recovery/export workflows are outside
|
|
12
|
+
this first package slice.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type WalletAccountStatus = "active" | "disabled" | "revoked" | "compromised";
|
|
2
|
+
export interface SignerRef {
|
|
3
|
+
readonly value: string;
|
|
4
|
+
readonly walletId: string;
|
|
5
|
+
readonly ownerId: string;
|
|
6
|
+
readonly agentId: string;
|
|
7
|
+
readonly scopes: readonly string[];
|
|
8
|
+
readonly createdAt: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WalletAccount {
|
|
11
|
+
readonly walletId: string;
|
|
12
|
+
readonly address: string;
|
|
13
|
+
readonly signerRef: SignerRef;
|
|
14
|
+
readonly status: WalletAccountStatus;
|
|
15
|
+
readonly allowedScopes: readonly string[];
|
|
16
|
+
readonly ownerId: string;
|
|
17
|
+
readonly agentId: string;
|
|
18
|
+
readonly createdAt: string;
|
|
19
|
+
readonly profileId?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface WalletCreationContext {
|
|
22
|
+
readonly ownerId: string;
|
|
23
|
+
readonly agentId: string;
|
|
24
|
+
readonly requestedScopes?: readonly string[];
|
|
25
|
+
readonly profileId?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface SignerAdapter {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
authorizeSigning(request: SigningAuthorizationRequest): Promise<SigningAuthorizationResult>;
|
|
30
|
+
}
|
|
31
|
+
export interface RecoveryPolicy {
|
|
32
|
+
readonly exportEnabled: false;
|
|
33
|
+
readonly reason: "unsupported" | "operator_required";
|
|
34
|
+
}
|
|
35
|
+
export interface SigningAuthorizationRequest {
|
|
36
|
+
readonly signerRef: string;
|
|
37
|
+
readonly walletId?: string;
|
|
38
|
+
readonly ownerId?: string;
|
|
39
|
+
readonly agentId?: string;
|
|
40
|
+
readonly scope?: string;
|
|
41
|
+
}
|
|
42
|
+
export type SigningAuthorizationResult = {
|
|
43
|
+
readonly authorized: true;
|
|
44
|
+
readonly walletId: string;
|
|
45
|
+
readonly signerRef: SignerRef;
|
|
46
|
+
} | {
|
|
47
|
+
readonly authorized: false;
|
|
48
|
+
readonly reasonCode: "OWNER_CONTEXT_REQUIRED" | "AGENT_CONTEXT_REQUIRED" | "WALLET_NOT_FOUND" | "SIGNER_REF_NOT_FOUND" | "SIGNER_REF_MISMATCH" | "SCOPE_NOT_ALLOWED" | "WALLET_NOT_ACTIVE";
|
|
49
|
+
readonly message: string;
|
|
50
|
+
};
|
|
51
|
+
export interface RecoveryExportRequest {
|
|
52
|
+
readonly walletId: string;
|
|
53
|
+
readonly actorId: string;
|
|
54
|
+
readonly reason: string;
|
|
55
|
+
}
|
|
56
|
+
export interface RecoveryExportDenied {
|
|
57
|
+
readonly allowed: false;
|
|
58
|
+
readonly reasonCode: "RECOVERY_EXPORT_UNSUPPORTED";
|
|
59
|
+
readonly message: string;
|
|
60
|
+
readonly audit: {
|
|
61
|
+
readonly walletId: string;
|
|
62
|
+
readonly actorId: string;
|
|
63
|
+
readonly reason: string;
|
|
64
|
+
readonly requestedAt: string;
|
|
65
|
+
readonly destinationType: "none";
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export interface WalletAccountStore extends SignerAdapter {
|
|
69
|
+
createWallet(context: WalletCreationContext): Promise<WalletAccount>;
|
|
70
|
+
getWallet(walletId: string): Promise<WalletAccount | undefined>;
|
|
71
|
+
setWalletStatus(walletId: string, status: WalletAccountStatus): Promise<WalletAccount | undefined>;
|
|
72
|
+
requestRecoveryExport(request: RecoveryExportRequest): Promise<RecoveryExportDenied>;
|
|
73
|
+
}
|
|
74
|
+
export interface InMemoryWalletAccountStoreOptions {
|
|
75
|
+
readonly now?: () => Date;
|
|
76
|
+
readonly idBytes?: () => Uint8Array;
|
|
77
|
+
readonly maxWalletsPerOwnerAgent?: number;
|
|
78
|
+
}
|
|
79
|
+
export declare function createInMemoryWalletAccountStore(options?: InMemoryWalletAccountStoreOptions): WalletAccountStore;
|
|
80
|
+
export declare function redactAccountValue(value: unknown): unknown;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
const EMPTY_SCOPES = Object.freeze([]);
|
|
3
|
+
export function createInMemoryWalletAccountStore(options = {}) {
|
|
4
|
+
const accounts = new Map();
|
|
5
|
+
const now = options.now ?? (() => new Date());
|
|
6
|
+
const idBytes = options.idBytes ?? (() => randomBytes(32));
|
|
7
|
+
const maxWalletsPerOwnerAgent = options.maxWalletsPerOwnerAgent ?? Number.POSITIVE_INFINITY;
|
|
8
|
+
function createOpaqueId(prefix) {
|
|
9
|
+
return `${prefix}_${randomUUID().replaceAll("-", "")}`;
|
|
10
|
+
}
|
|
11
|
+
function createAddress() {
|
|
12
|
+
const digest = createHash("sha256").update(idBytes()).digest("hex");
|
|
13
|
+
return `0x${digest}`;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
id: "in-memory-wallet-account-store",
|
|
17
|
+
async createWallet(context) {
|
|
18
|
+
const ownerId = normalizeRequiredContext("ownerId", context.ownerId);
|
|
19
|
+
const agentId = normalizeRequiredContext("agentId", context.agentId);
|
|
20
|
+
const existingForContext = [...accounts.values()].filter((account) => account.ownerId === ownerId && account.agentId === agentId).length;
|
|
21
|
+
if (existingForContext >= maxWalletsPerOwnerAgent) {
|
|
22
|
+
throw new Error("wallet creation limit exceeded for owner and agent context.");
|
|
23
|
+
}
|
|
24
|
+
const walletId = createOpaqueId("wallet");
|
|
25
|
+
const createdAt = now().toISOString();
|
|
26
|
+
const allowedScopes = [...new Set(context.requestedScopes ?? EMPTY_SCOPES)];
|
|
27
|
+
const signerRef = Object.freeze({
|
|
28
|
+
value: createOpaqueId("signer_ref"),
|
|
29
|
+
walletId,
|
|
30
|
+
ownerId,
|
|
31
|
+
agentId,
|
|
32
|
+
scopes: Object.freeze([...allowedScopes]),
|
|
33
|
+
createdAt,
|
|
34
|
+
});
|
|
35
|
+
const account = Object.freeze({
|
|
36
|
+
walletId,
|
|
37
|
+
address: createAddress(),
|
|
38
|
+
signerRef,
|
|
39
|
+
status: "active",
|
|
40
|
+
allowedScopes: Object.freeze([...allowedScopes]),
|
|
41
|
+
ownerId,
|
|
42
|
+
agentId,
|
|
43
|
+
createdAt,
|
|
44
|
+
...(context.profileId ? { profileId: context.profileId } : {}),
|
|
45
|
+
});
|
|
46
|
+
accounts.set(walletId, account);
|
|
47
|
+
return account;
|
|
48
|
+
},
|
|
49
|
+
async getWallet(walletId) {
|
|
50
|
+
return accounts.get(walletId);
|
|
51
|
+
},
|
|
52
|
+
async setWalletStatus(walletId, status) {
|
|
53
|
+
const account = accounts.get(walletId);
|
|
54
|
+
if (!account)
|
|
55
|
+
return undefined;
|
|
56
|
+
const updated = Object.freeze({ ...account, status });
|
|
57
|
+
accounts.set(walletId, updated);
|
|
58
|
+
return updated;
|
|
59
|
+
},
|
|
60
|
+
async authorizeSigning(request) {
|
|
61
|
+
if (!request.ownerId) {
|
|
62
|
+
return deny("OWNER_CONTEXT_REQUIRED", "Owner context is required before using a signer reference.");
|
|
63
|
+
}
|
|
64
|
+
if (!request.agentId) {
|
|
65
|
+
return deny("AGENT_CONTEXT_REQUIRED", "Agent context is required before using a signer reference.");
|
|
66
|
+
}
|
|
67
|
+
const account = findAccountBySignerRef(accounts, request.signerRef);
|
|
68
|
+
if (!account)
|
|
69
|
+
return deny("SIGNER_REF_NOT_FOUND", "Signer reference was not found.");
|
|
70
|
+
if (request.walletId && request.walletId !== account.walletId) {
|
|
71
|
+
return deny("SIGNER_REF_MISMATCH", "Signer reference does not belong to the requested wallet.");
|
|
72
|
+
}
|
|
73
|
+
if (request.ownerId !== account.ownerId || request.agentId !== account.agentId) {
|
|
74
|
+
return deny("SIGNER_REF_MISMATCH", "Signer reference context does not match the wallet account.");
|
|
75
|
+
}
|
|
76
|
+
if (account.status !== "active") {
|
|
77
|
+
return deny("WALLET_NOT_ACTIVE", "Wallet account is not active.");
|
|
78
|
+
}
|
|
79
|
+
if (request.scope && !account.allowedScopes.includes(request.scope)) {
|
|
80
|
+
return deny("SCOPE_NOT_ALLOWED", "Requested signing scope is not allowed for this wallet.");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
authorized: true,
|
|
84
|
+
walletId: account.walletId,
|
|
85
|
+
signerRef: account.signerRef,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
async requestRecoveryExport(request) {
|
|
89
|
+
return {
|
|
90
|
+
allowed: false,
|
|
91
|
+
reasonCode: "RECOVERY_EXPORT_UNSUPPORTED",
|
|
92
|
+
message: "Recovery export is unsupported for autonomous agent runtime flows.",
|
|
93
|
+
audit: {
|
|
94
|
+
walletId: request.walletId,
|
|
95
|
+
actorId: request.actorId,
|
|
96
|
+
reason: request.reason,
|
|
97
|
+
requestedAt: now().toISOString(),
|
|
98
|
+
destinationType: "none",
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function redactAccountValue(value) {
|
|
105
|
+
if (typeof value === "string") {
|
|
106
|
+
return redactString(value);
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return value.map((item) => redactAccountValue(item));
|
|
110
|
+
}
|
|
111
|
+
if (value && typeof value === "object") {
|
|
112
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, shouldRedactKey(key) ? "[REDACTED]" : redactAccountValue(item)]));
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
function normalizeRequiredContext(name, value) {
|
|
117
|
+
const normalized = value.trim();
|
|
118
|
+
if (!normalized)
|
|
119
|
+
throw new Error(`${name} is required for wallet creation.`);
|
|
120
|
+
return normalized;
|
|
121
|
+
}
|
|
122
|
+
function findAccountBySignerRef(accounts, signerRef) {
|
|
123
|
+
for (const account of accounts.values()) {
|
|
124
|
+
if (account.signerRef.value === signerRef)
|
|
125
|
+
return account;
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
function deny(reasonCode, message) {
|
|
130
|
+
return { authorized: false, reasonCode, message };
|
|
131
|
+
}
|
|
132
|
+
function redactString(value) {
|
|
133
|
+
return value
|
|
134
|
+
.replace(/\bsigner_ref_[A-Za-z0-9_:-]+\b/g, "signer_ref_[REDACTED]")
|
|
135
|
+
.replace(/\b(?:seed|mnemonic|private[_-]?key|raw[_-]?keypair|bearer|api[_-]?key)\b[^\s,;]*/gi, "[REDACTED]");
|
|
136
|
+
}
|
|
137
|
+
function shouldRedactKey(key) {
|
|
138
|
+
return /^(value|seed|mnemonic|privateKey|private_key|rawKeypair|raw_keypair|apiKey|api_key|bearerToken|bearer_token)$/i.test(key);
|
|
139
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vallum/accounts",
|
|
3
|
+
"version": "0.0.0-prerelease",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
|
+
"description": "Signer-reference-first account and wallet primitives for Vallum.",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/**/*.js",
|
|
17
|
+
"dist/**/*.d.ts",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.build.json"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"tag": "next"
|
|
31
|
+
}
|
|
32
|
+
}
|