@spectratools/tx-shared 0.4.3 → 0.5.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
@@ -16,13 +16,13 @@ This package is designed for consuming CLIs (for example `@spectratools/assembly
16
16
  pnpm add @spectratools/tx-shared
17
17
  ```
18
18
 
19
- ## Signer Providers
19
+ ## Signer providers
20
20
 
21
21
  `resolveSigner()` uses deterministic precedence:
22
22
 
23
23
  1. `privateKey`
24
24
  2. `keystorePath` (+ `keystorePassword`)
25
- 3. `privy` / `PRIVY_*`
25
+ 3. Privy (`privy` flag and/or `PRIVY_*` env)
26
26
 
27
27
  If no provider is configured, it throws `TxError` with code `SIGNER_NOT_CONFIGURED`.
28
28
 
@@ -50,13 +50,36 @@ export KEYSTORE_PASSWORD="..."
50
50
 
51
51
  #### 3) Privy
52
52
 
53
+ Required env:
54
+
53
55
  ```bash
54
56
  export PRIVY_APP_ID="..."
55
57
  export PRIVY_WALLET_ID="..."
56
- export PRIVY_AUTHORIZATION_KEY="..."
58
+ export PRIVY_AUTHORIZATION_KEY="wallet-auth:<base64-pkcs8-p256-private-key>"
59
+ ```
60
+
61
+ Optional env:
62
+
63
+ ```bash
64
+ export PRIVY_API_URL="https://api.sandbox.privy.io"
65
+ ```
66
+
67
+ Optional CLI flags (from `signerFlagSchema`):
68
+
69
+ ```bash
70
+ --privy
71
+ --privy-api-url "https://api.sandbox.privy.io"
57
72
  ```
58
73
 
59
- > Privy signer resolution now performs wallet address lookup and exposes a Privy-backed account helper for `eth_sendTransaction` intents. Additional signing methods (for example `personal_sign` and typed-data) are tracked in [issue #117](https://github.com/spectra-the-bot/spectra-tools/issues/117).
74
+ Notes:
75
+
76
+ - `--privy` is optional when `PRIVY_APP_ID` + `PRIVY_WALLET_ID` + `PRIVY_AUTHORIZATION_KEY` are present; `resolveSigner()` auto-detects Privy config.
77
+ - `--privy-api-url` / `PRIVY_API_URL` override the default `https://api.privy.io`.
78
+ - Privy account methods are mapped as:
79
+ - `sendTransaction` → `eth_sendTransaction`
80
+ - `signMessage` → `personal_sign`
81
+ - `signTypedData` → `eth_signTypedData_v4`
82
+ - `signTransaction` → `eth_signTransaction` (returns serialized tx hex; broadcast separately if needed)
60
83
 
61
84
  ## `resolveSigner()` usage
62
85
 
@@ -70,7 +93,7 @@ const signer = await resolveSigner({
70
93
  });
71
94
 
72
95
  console.log(signer.provider); // 'private-key' | 'keystore' | 'privy'
73
- console.log(signer.address); // 0x...
96
+ console.log(signer.address); // 0x...
74
97
  ```
75
98
 
76
99
  ### From shared CLI flags + env
@@ -84,8 +107,8 @@ import {
84
107
  } from '@spectratools/tx-shared';
85
108
 
86
109
  const flags = signerFlagSchema.parse({
87
- 'private-key': process.env.PRIVATE_KEY,
88
- privy: false,
110
+ privy: true,
111
+ 'privy-api-url': process.env.PRIVY_API_URL,
89
112
  });
90
113
 
91
114
  const env = signerEnvSchema.parse(process.env);
@@ -98,46 +121,108 @@ const signer = await resolveSigner(toSignerOptions(flags, env));
98
121
 
99
122
  1. estimate gas (`estimateContractGas`)
100
123
  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
124
+ 3. preflight Privy policies (when signer is Privy-backed)
125
+ 4. submit (`writeContract`) unless `dryRun: true`
126
+ 5. wait for receipt (`waitForTransactionReceipt`)
127
+ 6. normalize result into a shared output shape
104
128
 
105
- ### Live transaction example
129
+ ### Privy transaction flow example (dry-run + live)
106
130
 
107
131
  ```ts
108
- import { executeTx } from '@spectratools/tx-shared';
132
+ import { http, createPublicClient, createWalletClient, parseAbi } from 'viem';
133
+ import {
134
+ abstractMainnet,
135
+ executeTx,
136
+ resolveSigner,
137
+ signerEnvSchema,
138
+ signerFlagSchema,
139
+ toSignerOptions,
140
+ } from '@spectratools/tx-shared';
141
+
142
+ const flags = signerFlagSchema.parse({
143
+ privy: true,
144
+ 'privy-api-url': process.env.PRIVY_API_URL,
145
+ });
146
+ const env = signerEnvSchema.parse(process.env);
147
+ const signer = await resolveSigner(toSignerOptions(flags, env));
148
+
149
+ const publicClient = createPublicClient({
150
+ chain: abstractMainnet,
151
+ transport: http(process.env.ABSTRACT_RPC_URL),
152
+ });
153
+ const walletClient = createWalletClient({
154
+ account: signer.account,
155
+ chain: abstractMainnet,
156
+ transport: http(process.env.ABSTRACT_RPC_URL),
157
+ });
158
+
159
+ const registryAbi = parseAbi(['function register() payable']);
109
160
 
110
- const result = await executeTx({
161
+ const dryRun = await executeTx({
111
162
  publicClient,
112
163
  walletClient,
113
164
  account: signer.account,
114
- address,
115
- abi,
165
+ address: '0x1111111111111111111111111111111111111111',
166
+ abi: registryAbi,
116
167
  functionName: 'register',
117
- value: registrationFee,
168
+ value: 1000000000000000n,
169
+ dryRun: true,
118
170
  });
119
171
 
120
- console.log(result.hash, result.status, result.gasUsed.toString());
121
- ```
122
-
123
- ### Dry-run example
172
+ if (dryRun.status === 'dry-run') {
173
+ console.log(dryRun.estimatedGas.toString());
174
+ console.log(dryRun.privyPolicy?.status); // 'allowed' | 'blocked'
175
+ }
124
176
 
125
- ```ts
126
- const result = await executeTx({
177
+ const live = await executeTx({
127
178
  publicClient,
128
179
  walletClient,
129
180
  account: signer.account,
130
- address,
131
- abi,
181
+ address: '0x1111111111111111111111111111111111111111',
182
+ abi: registryAbi,
132
183
  functionName: 'register',
133
- value: registrationFee,
134
- dryRun: true,
184
+ value: 1000000000000000n,
135
185
  });
136
186
 
137
- if (result.status === 'dry-run') {
138
- console.log('estimatedGas', result.estimatedGas.toString());
139
- console.log('simulationResult', result.simulationResult);
140
- }
187
+ console.log(live.hash, live.status);
188
+ ```
189
+
190
+ ### Privy signing flow example
191
+
192
+ ```ts
193
+ const signer = await resolveSigner({
194
+ privy: true,
195
+ privyAppId: process.env.PRIVY_APP_ID,
196
+ privyWalletId: process.env.PRIVY_WALLET_ID,
197
+ privyAuthorizationKey: process.env.PRIVY_AUTHORIZATION_KEY,
198
+ privyApiUrl: process.env.PRIVY_API_URL,
199
+ });
200
+
201
+ if (signer.provider !== 'privy') throw new Error('Expected Privy signer');
202
+
203
+ const messageSig = await signer.account.signMessage({ message: 'hello from tx-shared' });
204
+
205
+ const typedDataSig = await signer.account.signTypedData({
206
+ domain: { name: 'SpectraTools', version: '1', chainId: 2741 },
207
+ primaryType: 'Ping',
208
+ types: {
209
+ Ping: [{ name: 'message', type: 'string' }],
210
+ },
211
+ message: { message: 'privy typed data' },
212
+ });
213
+
214
+ const signedTxHex = await signer.account.signTransaction({
215
+ to: '0x1111111111111111111111111111111111111111',
216
+ chainId: 2741,
217
+ nonce: 1,
218
+ gas: 21000n,
219
+ maxFeePerGas: 1000000000n,
220
+ maxPriorityFeePerGas: 1000000000n,
221
+ value: 0n,
222
+ data: '0x',
223
+ });
224
+
225
+ console.log(messageSig, typedDataSig, signedTxHex);
141
226
  ```
142
227
 
143
228
  ## Structured errors
@@ -151,6 +236,8 @@ if (result.status === 'dry-run') {
151
236
  - `SIGNER_NOT_CONFIGURED`
152
237
  - `KEYSTORE_DECRYPT_FAILED`
153
238
  - `PRIVY_AUTH_FAILED`
239
+ - `PRIVY_TRANSPORT_FAILED`
240
+ - `PRIVY_POLICY_BLOCKED`
154
241
 
155
242
  ```ts
156
243
  import { TxError } from '@spectratools/tx-shared';
@@ -167,6 +254,8 @@ try {
167
254
  case 'SIGNER_NOT_CONFIGURED':
168
255
  case 'KEYSTORE_DECRYPT_FAILED':
169
256
  case 'PRIVY_AUTH_FAILED':
257
+ case 'PRIVY_TRANSPORT_FAILED':
258
+ case 'PRIVY_POLICY_BLOCKED':
170
259
  throw error;
171
260
  }
172
261
  }
@@ -184,8 +273,15 @@ try {
184
273
  - verify file path and password
185
274
  - ensure keystore is valid V3 JSON
186
275
  - **`PRIVY_AUTH_FAILED`**
187
- - verify all `PRIVY_*` variables are set
188
- - check signer/owner policy constraints for the Privy wallet
276
+ - verify required env: `PRIVY_APP_ID`, `PRIVY_WALLET_ID`, `PRIVY_AUTHORIZATION_KEY`
277
+ - validate `PRIVY_AUTHORIZATION_KEY` format: `wallet-auth:<base64-pkcs8-p256-private-key>`
278
+ - verify `PRIVY_API_URL` override points at a valid Privy API host
279
+ - **`PRIVY_TRANSPORT_FAILED`**
280
+ - check Privy API availability and credentials
281
+ - inspect upstream intent failure payloads for status + reason
282
+ - **`PRIVY_POLICY_BLOCKED`**
283
+ - review active wallet policy allowlists and native value caps
284
+ - use `dryRun: true` and inspect `result.privyPolicy` before broadcasting
189
285
  - **`GAS_ESTIMATION_FAILED` / `TX_REVERTED`**
190
286
  - validate function args and `value`
191
287
  - run with `dryRun: true` first