@equalfi/ski 0.1.1 → 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
@@ -15,6 +15,39 @@ pnpm dlx @equalfi/ski
15
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 { Entry } = require('@napi-rs/keyring');
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,44 +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
- const entry = new Entry(SERVICE, keychainAccount(label));
106
- entry.setPassword(password);
72
+ created = await createSessionKey({ label, entityId, allowOverwrite: true });
107
73
  } catch (err) {
108
- console.error('Failed to store key in OS keychain:', err?.message || err);
109
- console.error('Keystore saved, but you must secure the password manually.');
74
+ console.error('Failed to create session key:', err?.message || err);
110
75
  return;
111
76
  }
112
77
 
113
78
  console.log('\n✅ Session key created');
114
- console.log(`Address: ${wallet.address}`);
79
+ console.log(`Address: ${created.address}`);
115
80
  console.log(`Label: ${label}`);
116
81
  console.log(`Entity: ${entityId}`);
117
- console.log(`Stored: ${keystorePath}`);
82
+ console.log(`Stored: ${created.keystorePath}`);
118
83
  console.log('\nPaste the address into the UI session key field and install the module/policy.');
119
84
  }
120
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.1",
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": {