@equalfi/ski 0.1.0 → 0.1.2

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 CHANGED
@@ -12,9 +12,42 @@ pnpm dlx @equalfi/ski
12
12
  - Prompts for Position NFT id and Validation Entity ID
13
13
  - Generates a fresh session EOA
14
14
  - Stores encrypted keystore at `~/.openclaw/keys/equalfi/<label>.json`
15
- - Stores the password in your OS keychain (one prompt)
15
+ - Stores the password in your OS keychain (one prompt) via @napi-rs/keyring
16
16
  - Prints the session key address to paste into the UI
17
17
 
18
+ ## Runtime helper (for agents)
19
+ This package also exposes helpers to execute calls via `executeWithRuntimeValidation` using the session key.
20
+
21
+ ```js
22
+ const { loadSessionWallet, buildRuntimeAuthorization, executeWithRuntimeValidation } = require('@equalfi/ski');
23
+
24
+ const wallet = await loadSessionWallet('position-1');
25
+ // build runtime authorization
26
+ const data = '0x...'; // ABI-encoded call (e.g., nonce())
27
+ const { authorization } = await buildRuntimeAuthorization({
28
+ moduleAddress: '0xSessionKeyValidationModule',
29
+ account: '0xTBA',
30
+ entityId: 7,
31
+ sender: wallet.address,
32
+ value: 0n,
33
+ data,
34
+ sessionWallet: wallet,
35
+ chainId: 31337,
36
+ });
37
+
38
+ // or send directly
39
+ await executeWithRuntimeValidation({
40
+ rpcUrl: 'http://127.0.0.1:8545',
41
+ tbaAddress: '0xTBA',
42
+ moduleAddress: '0xSessionKeyValidationModule',
43
+ entityId: 7,
44
+ data,
45
+ value: 0n,
46
+ sessionWallet: wallet,
47
+ chainId: 31337,
48
+ });
49
+ ```
50
+
18
51
  ## Notes
19
52
  - Default label is `position-<tokenId>`
20
53
  - If the key already exists, it asks before overwriting
package/bin/cli.js CHANGED
@@ -5,15 +5,12 @@ const path = require('path');
5
5
  const os = require('os');
6
6
  const crypto = require('crypto');
7
7
  const prompts = require('prompts');
8
- const keytar = require('keytar');
9
- const { Wallet } = require('ethers');
8
+ const { createSessionKey } = require('../lib/session');
10
9
 
11
- const SERVICE = 'equalfi-ski';
12
- const SKILL_ID = 'equalfi';
13
10
  const DEFAULT_ENTITY_ID = 7;
14
11
 
15
12
  const resolvePaths = (label) => {
16
- const baseDir = path.join(os.homedir(), '.openclaw', 'keys', SKILL_ID);
13
+ const baseDir = path.join(os.homedir(), '.openclaw', 'keys', 'equalfi');
17
14
  return {
18
15
  baseDir,
19
16
  keystorePath: path.join(baseDir, `${label}.json`),
@@ -21,12 +18,6 @@ const resolvePaths = (label) => {
21
18
  };
22
19
  };
23
20
 
24
- const keychainAccount = (label) => `session-key:${SKILL_ID}:${label}`;
25
-
26
- async function ensureDir(dir) {
27
- await fs.mkdir(dir, { recursive: true });
28
- }
29
-
30
21
  async function fileExists(p) {
31
22
  try {
32
23
  await fs.access(p);
@@ -61,8 +52,7 @@ async function main() {
61
52
  const label = `position-${response.tokenId}`;
62
53
  const entityId = Number(response.entityId ?? DEFAULT_ENTITY_ID);
63
54
 
64
- const { baseDir, keystorePath, metaPath } = resolvePaths(label);
65
- await ensureDir(baseDir);
55
+ const { keystorePath } = resolvePaths(label);
66
56
 
67
57
  if (await fileExists(keystorePath)) {
68
58
  const confirm = await prompts({
@@ -77,43 +67,19 @@ async function main() {
77
67
  }
78
68
  }
79
69
 
80
- const wallet = Wallet.createRandom();
81
- const password = crypto.randomBytes(32).toString('hex');
82
- const keystore = await wallet.encrypt(password);
83
-
84
- await fs.writeFile(keystorePath, keystore);
85
- await fs.writeFile(
86
- metaPath,
87
- JSON.stringify(
88
- {
89
- address: wallet.address,
90
- chainId: null,
91
- skillId: SKILL_ID,
92
- agentLabel: label,
93
- positionTokenId: response.tokenId,
94
- entityId,
95
- createdAt: new Date().toISOString(),
96
- keychainService: SERVICE,
97
- keychainAccount: keychainAccount(label),
98
- },
99
- null,
100
- 2
101
- )
102
- );
103
-
70
+ let created;
104
71
  try {
105
- await keytar.setPassword(SERVICE, keychainAccount(label), password);
72
+ created = await createSessionKey({ label, entityId, allowOverwrite: true });
106
73
  } catch (err) {
107
- console.error('Failed to store key in OS keychain:', err?.message || err);
108
- console.error('Keystore saved, but you must secure the password manually.');
74
+ console.error('Failed to create session key:', err?.message || err);
109
75
  return;
110
76
  }
111
77
 
112
78
  console.log('\n✅ Session key created');
113
- console.log(`Address: ${wallet.address}`);
79
+ console.log(`Address: ${created.address}`);
114
80
  console.log(`Label: ${label}`);
115
81
  console.log(`Entity: ${entityId}`);
116
- console.log(`Stored: ${keystorePath}`);
82
+ console.log(`Stored: ${created.keystorePath}`);
117
83
  console.log('\nPaste the address into the UI session key field and install the module/policy.');
118
84
  }
119
85
 
package/lib/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./session');
package/lib/session.js ADDED
@@ -0,0 +1,152 @@
1
+ const fs = require('fs/promises');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const crypto = require('crypto');
5
+ const { Entry } = require('@napi-rs/keyring');
6
+ const { Wallet, Contract, JsonRpcProvider, AbiCoder, keccak256, toUtf8Bytes, getBytes } = require('ethers');
7
+
8
+ const SERVICE = 'equalfi-ski';
9
+ const SKILL_ID = 'equalfi';
10
+
11
+ const resolvePaths = (label) => {
12
+ const baseDir = path.join(os.homedir(), '.openclaw', 'keys', SKILL_ID);
13
+ return {
14
+ baseDir,
15
+ keystorePath: path.join(baseDir, `${label}.json`),
16
+ metaPath: path.join(baseDir, `${label}.meta.json`),
17
+ };
18
+ };
19
+
20
+ const keychainAccount = (label) => `session-key:${SKILL_ID}:${label}`;
21
+
22
+ async function ensureDir(dir) {
23
+ await fs.mkdir(dir, { recursive: true });
24
+ }
25
+
26
+ async function fileExists(p) {
27
+ try {
28
+ await fs.access(p);
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function moduleEntity(module, entityId) {
36
+ const clean = module.toLowerCase().replace(/^0x/, '');
37
+ const entityHex = BigInt(entityId).toString(16).padStart(8, '0');
38
+ return `0x${clean}${entityHex}`; // bytes24
39
+ }
40
+
41
+ async function createSessionKey({ label, entityId = 7, allowOverwrite = false }) {
42
+ const { baseDir, keystorePath, metaPath } = resolvePaths(label);
43
+ await ensureDir(baseDir);
44
+
45
+ if (!allowOverwrite && (await fileExists(keystorePath))) {
46
+ throw new Error(`Key already exists for ${label}.`);
47
+ }
48
+
49
+ const wallet = Wallet.createRandom();
50
+ const password = crypto.randomBytes(32).toString('hex');
51
+ const keystore = await wallet.encrypt(password);
52
+
53
+ await fs.writeFile(keystorePath, keystore);
54
+ await fs.writeFile(
55
+ metaPath,
56
+ JSON.stringify(
57
+ {
58
+ address: wallet.address,
59
+ chainId: null,
60
+ skillId: SKILL_ID,
61
+ agentLabel: label,
62
+ entityId,
63
+ createdAt: new Date().toISOString(),
64
+ keychainService: SERVICE,
65
+ keychainAccount: keychainAccount(label),
66
+ },
67
+ null,
68
+ 2
69
+ )
70
+ );
71
+
72
+ const entry = new Entry(SERVICE, keychainAccount(label));
73
+ entry.setPassword(password);
74
+
75
+ return { address: wallet.address, keystorePath, metaPath };
76
+ }
77
+
78
+ async function loadSessionWallet(label) {
79
+ const { keystorePath } = resolvePaths(label);
80
+ const entry = new Entry(SERVICE, keychainAccount(label));
81
+ const password = entry.getPassword();
82
+ if (!password) {
83
+ throw new Error(`Missing keychain entry for ${label}`);
84
+ }
85
+ const keystore = await fs.readFile(keystorePath, 'utf8');
86
+ const wallet = await Wallet.fromEncryptedJson(keystore, password);
87
+ return wallet;
88
+ }
89
+
90
+ function buildRuntimeAuthorization({
91
+ moduleAddress,
92
+ account,
93
+ entityId,
94
+ sender,
95
+ value = 0n,
96
+ data,
97
+ sessionWallet,
98
+ replayProtection,
99
+ chainId,
100
+ }) {
101
+ const moduleEnt = moduleEntity(moduleAddress, entityId);
102
+ const replay = replayProtection || `0x${crypto.randomBytes(32).toString('hex')}`;
103
+ const coder = AbiCoder.defaultAbiCoder();
104
+ const tag = keccak256(toUtf8Bytes('AGENT_WALLET_SESSION_RUNTIME_V1'));
105
+ const payload = coder.encode(
106
+ ['bytes32','uint256','address','address','uint32','address','uint256','bytes32','bytes32'],
107
+ [tag, BigInt(chainId), moduleAddress, account, entityId, sender, BigInt(value), keccak256(data), replay]
108
+ );
109
+ const payloadHash = keccak256(payload);
110
+ const signature = sessionWallet.signMessageSync ? sessionWallet.signMessageSync(getBytes(payloadHash)) : sessionWallet.signMessage(getBytes(payloadHash));
111
+ const moduleAuth = coder.encode(['address','bytes32','bytes'], [sessionWallet.address, replay, signature]);
112
+ const authorization = coder.encode(['bytes24','bytes'], [moduleEnt, moduleAuth]);
113
+ return { authorization, replayProtection: replay, signature, moduleEntity: moduleEnt };
114
+ }
115
+
116
+ async function executeWithRuntimeValidation({
117
+ rpcUrl,
118
+ tbaAddress,
119
+ moduleAddress,
120
+ entityId,
121
+ data,
122
+ value = 0n,
123
+ sessionWallet,
124
+ chainId,
125
+ }) {
126
+ const provider = new JsonRpcProvider(rpcUrl);
127
+ const wallet = sessionWallet.connect(provider);
128
+ const { authorization } = await buildRuntimeAuthorization({
129
+ moduleAddress,
130
+ account: tbaAddress,
131
+ entityId,
132
+ sender: wallet.address,
133
+ value,
134
+ data,
135
+ sessionWallet: wallet,
136
+ chainId,
137
+ });
138
+
139
+ const tba = new Contract(
140
+ tbaAddress,
141
+ ['function executeWithRuntimeValidation(bytes data, bytes authorization) payable returns (bytes)'],
142
+ wallet
143
+ );
144
+ return tba.executeWithRuntimeValidation(data, authorization, { value });
145
+ }
146
+
147
+ module.exports = {
148
+ createSessionKey,
149
+ loadSessionWallet,
150
+ buildRuntimeAuthorization,
151
+ executeWithRuntimeValidation,
152
+ };
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@equalfi/ski",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "EqualFi Session Key Installer (interactive CLI)",
5
5
  "license": "MIT",
6
+ "main": "lib/index.js",
6
7
  "bin": {
7
8
  "ski": "bin/cli.js"
8
9
  },
9
10
  "files": [
10
11
  "bin",
12
+ "lib",
11
13
  "README.md"
12
14
  ],
13
15
  "engines": {
@@ -15,7 +17,7 @@
15
17
  },
16
18
  "dependencies": {
17
19
  "ethers": "^6.13.5",
18
- "keytar": "^7.9.0",
20
+ "@napi-rs/keyring": "^1.2.0",
19
21
  "prompts": "^2.4.2"
20
22
  }
21
23
  }