@spectratools/tx-shared 0.3.0 → 0.4.1

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
@@ -1,3 +1,200 @@
1
1
  # @spectratools/tx-shared
2
2
 
3
- Shared transaction primitives for Spectra tools: signer/result types, structured tx errors, and Abstract chain client helpers.
3
+ Shared transaction primitives for Spectra tools:
4
+
5
+ - signer resolution (`resolveSigner`)
6
+ - transaction lifecycle execution (`executeTx`)
7
+ - shared signer CLI/env parsing helpers (`toSignerOptions`)
8
+ - structured transaction errors (`TxError`)
9
+ - Abstract chain helpers (`abstractMainnet`, `createAbstractClient`)
10
+
11
+ This package is designed for consuming CLIs (for example `@spectratools/assembly-cli`) so write-capable commands follow the same signer precedence, dry-run behavior, and error handling.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pnpm add @spectratools/tx-shared
17
+ ```
18
+
19
+ ## Signer Providers
20
+
21
+ `resolveSigner()` uses deterministic precedence:
22
+
23
+ 1. `privateKey`
24
+ 2. `keystorePath` (+ `keystorePassword`)
25
+ 3. `privy` / `PRIVY_*`
26
+
27
+ If no provider is configured, it throws `TxError` with code `SIGNER_NOT_CONFIGURED`.
28
+
29
+ ### Provider setup
30
+
31
+ #### 1) Private key
32
+
33
+ ```bash
34
+ export PRIVATE_KEY="0x<64-hex-chars>"
35
+ ```
36
+
37
+ Or pass `privateKey` directly in code.
38
+
39
+ #### 2) Keystore
40
+
41
+ ```bash
42
+ # CLI convention
43
+ --keystore /path/to/keystore.json --password "$KEYSTORE_PASSWORD"
44
+
45
+ # env fallback for password
46
+ export KEYSTORE_PASSWORD="..."
47
+ ```
48
+
49
+ `resolveSigner()` requires a password when `keystorePath` is provided.
50
+
51
+ #### 3) Privy
52
+
53
+ ```bash
54
+ export PRIVY_APP_ID="..."
55
+ export PRIVY_WALLET_ID="..."
56
+ export PRIVY_AUTHORIZATION_KEY="..."
57
+ ```
58
+
59
+ > Privy signer execution is intentionally stubbed right now and throws `PRIVY_AUTH_FAILED` with a deterministic message. Full implementation is tracked in [issue #117](https://github.com/spectra-the-bot/spectra-tools/issues/117).
60
+
61
+ ## `resolveSigner()` usage
62
+
63
+ ### Direct options
64
+
65
+ ```ts
66
+ import { resolveSigner } from '@spectratools/tx-shared';
67
+
68
+ const signer = await resolveSigner({
69
+ privateKey: process.env.PRIVATE_KEY,
70
+ });
71
+
72
+ console.log(signer.provider); // 'private-key' | 'keystore' | 'privy'
73
+ console.log(signer.address); // 0x...
74
+ ```
75
+
76
+ ### From shared CLI flags + env
77
+
78
+ ```ts
79
+ import {
80
+ resolveSigner,
81
+ signerEnvSchema,
82
+ signerFlagSchema,
83
+ toSignerOptions,
84
+ } from '@spectratools/tx-shared';
85
+
86
+ const flags = signerFlagSchema.parse({
87
+ 'private-key': process.env.PRIVATE_KEY,
88
+ privy: false,
89
+ });
90
+
91
+ const env = signerEnvSchema.parse(process.env);
92
+ const signer = await resolveSigner(toSignerOptions(flags, env));
93
+ ```
94
+
95
+ ## `executeTx()` lifecycle
96
+
97
+ `executeTx()` performs this flow:
98
+
99
+ 1. estimate gas (`estimateContractGas`)
100
+ 2. simulate (`simulateContract`)
101
+ 3. submit (`writeContract`) unless `dryRun: true`
102
+ 4. wait for receipt (`waitForTransactionReceipt`)
103
+ 5. normalize result into a shared output shape
104
+
105
+ ### Live transaction example
106
+
107
+ ```ts
108
+ import { executeTx } from '@spectratools/tx-shared';
109
+
110
+ const result = await executeTx({
111
+ publicClient,
112
+ walletClient,
113
+ account: signer.account,
114
+ address,
115
+ abi,
116
+ functionName: 'register',
117
+ value: registrationFee,
118
+ });
119
+
120
+ console.log(result.hash, result.status, result.gasUsed.toString());
121
+ ```
122
+
123
+ ### Dry-run example
124
+
125
+ ```ts
126
+ const result = await executeTx({
127
+ publicClient,
128
+ walletClient,
129
+ account: signer.account,
130
+ address,
131
+ abi,
132
+ functionName: 'register',
133
+ value: registrationFee,
134
+ dryRun: true,
135
+ });
136
+
137
+ if (result.status === 'dry-run') {
138
+ console.log('estimatedGas', result.estimatedGas.toString());
139
+ console.log('simulationResult', result.simulationResult);
140
+ }
141
+ ```
142
+
143
+ ## Structured errors
144
+
145
+ `executeTx()` and signer helpers throw `TxError` with stable `code` values:
146
+
147
+ - `INSUFFICIENT_FUNDS`
148
+ - `NONCE_CONFLICT`
149
+ - `GAS_ESTIMATION_FAILED`
150
+ - `TX_REVERTED`
151
+ - `SIGNER_NOT_CONFIGURED`
152
+ - `KEYSTORE_DECRYPT_FAILED`
153
+ - `PRIVY_AUTH_FAILED`
154
+
155
+ ```ts
156
+ import { TxError } from '@spectratools/tx-shared';
157
+
158
+ try {
159
+ // resolveSigner(...) + executeTx(...)
160
+ } catch (error) {
161
+ if (error instanceof TxError) {
162
+ switch (error.code) {
163
+ case 'INSUFFICIENT_FUNDS':
164
+ case 'NONCE_CONFLICT':
165
+ case 'GAS_ESTIMATION_FAILED':
166
+ case 'TX_REVERTED':
167
+ case 'SIGNER_NOT_CONFIGURED':
168
+ case 'KEYSTORE_DECRYPT_FAILED':
169
+ case 'PRIVY_AUTH_FAILED':
170
+ throw error;
171
+ }
172
+ }
173
+
174
+ throw error;
175
+ }
176
+ ```
177
+
178
+ ## Troubleshooting
179
+
180
+ - **`SIGNER_NOT_CONFIGURED`**
181
+ - check signer input precedence
182
+ - confirm at least one provider is configured
183
+ - **`KEYSTORE_DECRYPT_FAILED`**
184
+ - verify file path and password
185
+ - ensure keystore is valid V3 JSON
186
+ - **`PRIVY_AUTH_FAILED`**
187
+ - verify all `PRIVY_*` variables are set
188
+ - note: provider is not live yet (see #117)
189
+ - **`GAS_ESTIMATION_FAILED` / `TX_REVERTED`**
190
+ - validate function args and `value`
191
+ - run with `dryRun: true` first
192
+ - **`NONCE_CONFLICT`**
193
+ - refresh nonce and retry once with an explicit override if needed
194
+
195
+ ## Consumer integration examples
196
+
197
+ - tx-shared assembly-style example:
198
+ - [`src/examples/assembly-write.ts`](./src/examples/assembly-write.ts)
199
+ - assembly consumer reference wiring:
200
+ - [`../assembly/src/examples/tx-shared-register.ts`](../assembly/src/examples/tx-shared-register.ts)
package/dist/index.d.ts CHANGED
@@ -1,10 +1,37 @@
1
- import { TxSigner } from './types.js';
2
- export { SignerOptions, SignerProvider, TxResult } from './types.js';
1
+ import { SignerOptions, TxSigner } from './types.js';
2
+ export { SignerProvider, TxResult } from './types.js';
3
3
  export { TxError, TxErrorCode, toTxError } from './errors.js';
4
4
  export { abstractMainnet, createAbstractClient } from './chain.js';
5
5
  export { DryRunResult, ExecuteTxOptions, executeTx } from './execute-tx.js';
6
+ import { z } from 'incur';
6
7
  import 'viem';
7
8
 
9
+ /**
10
+ * Resolve the active signer provider using deterministic precedence:
11
+ * private key -> keystore -> privy -> SIGNER_NOT_CONFIGURED.
12
+ */
13
+ declare function resolveSigner(opts: SignerOptions): Promise<TxSigner>;
14
+
15
+ /** Shared signer-related CLI flags for write-capable commands. */
16
+ declare const signerFlagSchema: z.ZodObject<{
17
+ 'private-key': z.ZodOptional<z.ZodString>;
18
+ keystore: z.ZodOptional<z.ZodString>;
19
+ password: z.ZodOptional<z.ZodString>;
20
+ privy: z.ZodDefault<z.ZodBoolean>;
21
+ }, z.core.$strip>;
22
+ /** Shared signer-related environment variables for write-capable commands. */
23
+ declare const signerEnvSchema: z.ZodObject<{
24
+ PRIVATE_KEY: z.ZodOptional<z.ZodString>;
25
+ KEYSTORE_PASSWORD: z.ZodOptional<z.ZodString>;
26
+ PRIVY_APP_ID: z.ZodOptional<z.ZodString>;
27
+ PRIVY_WALLET_ID: z.ZodOptional<z.ZodString>;
28
+ PRIVY_AUTHORIZATION_KEY: z.ZodOptional<z.ZodString>;
29
+ }, z.core.$strip>;
30
+ type SignerFlags = z.infer<typeof signerFlagSchema>;
31
+ type SignerEnv = z.infer<typeof signerEnvSchema>;
32
+ /** Map parsed CLI context into tx-shared SignerOptions. */
33
+ declare function toSignerOptions(flags: SignerFlags, env: SignerEnv): SignerOptions;
34
+
8
35
  /**
9
36
  * Create a {@link TxSigner} from a raw private key.
10
37
  *
@@ -30,4 +57,17 @@ interface KeystoreSignerOptions {
30
57
  */
31
58
  declare function createKeystoreSigner(options: KeystoreSignerOptions): TxSigner;
32
59
 
33
- export { type KeystoreSignerOptions, TxSigner, createKeystoreSigner, createPrivateKeySigner };
60
+ interface PrivySignerOptions {
61
+ privyAppId?: string;
62
+ privyWalletId?: string;
63
+ privyAuthorizationKey?: string;
64
+ }
65
+ /**
66
+ * Privy signer adapter entrypoint.
67
+ *
68
+ * Full Privy integration is tracked in issue #117. Until that lands,
69
+ * this adapter provides deterministic, structured failures for callers.
70
+ */
71
+ declare function createPrivySigner(options: PrivySignerOptions): Promise<TxSigner>;
72
+
73
+ export { type KeystoreSignerOptions, type PrivySignerOptions, type SignerEnv, type SignerFlags, SignerOptions, TxSigner, createKeystoreSigner, createPrivateKeySigner, createPrivySigner, resolveSigner, signerEnvSchema, signerFlagSchema, toSignerOptions };
package/dist/index.js CHANGED
@@ -56,12 +56,108 @@ function createKeystoreSigner(options) {
56
56
  const account = privateKeyToAccount2(privateKey);
57
57
  return { account, address: account.address, provider: "keystore" };
58
58
  }
59
+
60
+ // src/signers/privy.ts
61
+ var REQUIRED_FIELDS = [
62
+ "privyAppId",
63
+ "privyWalletId",
64
+ "privyAuthorizationKey"
65
+ ];
66
+ async function createPrivySigner(options) {
67
+ const missing = REQUIRED_FIELDS.filter((field) => options[field] === void 0);
68
+ if (missing.length > 0) {
69
+ const missingLabels = missing.map((field) => {
70
+ if (field === "privyAppId") {
71
+ return "PRIVY_APP_ID";
72
+ }
73
+ if (field === "privyWalletId") {
74
+ return "PRIVY_WALLET_ID";
75
+ }
76
+ return "PRIVY_AUTHORIZATION_KEY";
77
+ }).join(", ");
78
+ throw new TxError(
79
+ "PRIVY_AUTH_FAILED",
80
+ `Privy signer requires configuration: missing ${missingLabels}`
81
+ );
82
+ }
83
+ throw new TxError(
84
+ "PRIVY_AUTH_FAILED",
85
+ "Privy signer is not yet available in tx-shared. Track progress in issue #117."
86
+ );
87
+ }
88
+
89
+ // src/resolve-signer.ts
90
+ function hasPrivyConfig(opts) {
91
+ return opts.privyAppId !== void 0 || opts.privyWalletId !== void 0 || opts.privyAuthorizationKey !== void 0;
92
+ }
93
+ async function resolveSigner(opts) {
94
+ if (opts.privateKey !== void 0) {
95
+ return createPrivateKeySigner(opts.privateKey);
96
+ }
97
+ if (opts.keystorePath !== void 0) {
98
+ if (opts.keystorePassword === void 0) {
99
+ throw new TxError(
100
+ "SIGNER_NOT_CONFIGURED",
101
+ "Keystore password is required when --keystore is provided (use --password or KEYSTORE_PASSWORD)."
102
+ );
103
+ }
104
+ return createKeystoreSigner({
105
+ keystorePath: opts.keystorePath,
106
+ keystorePassword: opts.keystorePassword
107
+ });
108
+ }
109
+ if (opts.privy === true || hasPrivyConfig(opts)) {
110
+ return createPrivySigner({
111
+ ...opts.privyAppId !== void 0 ? { privyAppId: opts.privyAppId } : {},
112
+ ...opts.privyWalletId !== void 0 ? { privyWalletId: opts.privyWalletId } : {},
113
+ ...opts.privyAuthorizationKey !== void 0 ? { privyAuthorizationKey: opts.privyAuthorizationKey } : {}
114
+ });
115
+ }
116
+ throw new TxError(
117
+ "SIGNER_NOT_CONFIGURED",
118
+ "No signer configured. Set --private-key, or --keystore + --password, or enable --privy with PRIVY_* credentials."
119
+ );
120
+ }
121
+
122
+ // src/incur-params.ts
123
+ import { z } from "incur";
124
+ var signerFlagSchema = z.object({
125
+ "private-key": z.string().optional().describe("Raw private key (0x-prefixed 32-byte hex) for signing transactions"),
126
+ keystore: z.string().optional().describe("Path to an encrypted V3 keystore JSON file"),
127
+ password: z.string().optional().describe("Keystore password (non-interactive mode)"),
128
+ privy: z.boolean().default(false).describe("Use Privy server wallet signer mode")
129
+ });
130
+ var signerEnvSchema = z.object({
131
+ PRIVATE_KEY: z.string().optional().describe("Raw private key (0x-prefixed 32-byte hex)"),
132
+ KEYSTORE_PASSWORD: z.string().optional().describe("Password for decrypting --keystore"),
133
+ PRIVY_APP_ID: z.string().optional().describe("Privy app id used to authorize wallet intents"),
134
+ PRIVY_WALLET_ID: z.string().optional().describe("Privy wallet id used for transaction intents"),
135
+ PRIVY_AUTHORIZATION_KEY: z.string().optional().describe("Privy authorization private key used to sign intent requests")
136
+ });
137
+ function toSignerOptions(flags, env) {
138
+ const privateKey = flags["private-key"] ?? env.PRIVATE_KEY;
139
+ const keystorePassword = flags.password ?? env.KEYSTORE_PASSWORD;
140
+ return {
141
+ ...privateKey !== void 0 ? { privateKey } : {},
142
+ ...flags.keystore !== void 0 ? { keystorePath: flags.keystore } : {},
143
+ ...keystorePassword !== void 0 ? { keystorePassword } : {},
144
+ ...flags.privy ? { privy: true } : {},
145
+ ...env.PRIVY_APP_ID !== void 0 ? { privyAppId: env.PRIVY_APP_ID } : {},
146
+ ...env.PRIVY_WALLET_ID !== void 0 ? { privyWalletId: env.PRIVY_WALLET_ID } : {},
147
+ ...env.PRIVY_AUTHORIZATION_KEY !== void 0 ? { privyAuthorizationKey: env.PRIVY_AUTHORIZATION_KEY } : {}
148
+ };
149
+ }
59
150
  export {
60
151
  TxError,
61
152
  abstractMainnet,
62
153
  createAbstractClient,
63
154
  createKeystoreSigner,
64
155
  createPrivateKeySigner,
156
+ createPrivySigner,
65
157
  executeTx,
158
+ resolveSigner,
159
+ signerEnvSchema,
160
+ signerFlagSchema,
161
+ toSignerOptions,
66
162
  toTxError
67
163
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectratools/tx-shared",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Shared transaction primitives, signer types, and chain config for spectra tools",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,6 +37,7 @@
37
37
  }
38
38
  },
39
39
  "dependencies": {
40
+ "incur": "^0.2.2",
40
41
  "ox": "^0.14.0",
41
42
  "viem": "^2.47.0"
42
43
  },