@altiuslabs/tx-sdk 0.1.20 → 0.1.22

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
@@ -14,7 +14,7 @@ npm install @altiuslabs/tx-sdk
14
14
 
15
15
  ```javascript
16
16
  import { TxClient, generate_private_key } from '@altiuslabs/tx-sdk';
17
- import { USDA_ADDRESS } from '@altius/tx-sdk/constants';
17
+ import { USDA_ADDRESS } from '@altiuslabs/tx-sdk';
18
18
 
19
19
  // Generate a new wallet
20
20
  const privateKey = generate_private_key();
@@ -56,28 +56,278 @@ const { tx_hash } = await client.send_erc20_transfer(
56
56
  const receipt = await client.send_and_wait(tx, 60000);
57
57
  ```
58
58
 
59
+ ---
60
+
59
61
  ## API Reference
60
62
 
61
- ### Core Classes
63
+ ### Wallet & Signing
62
64
 
63
- - **TxClient** - High-level client for sending transactions
64
- - **Wallet** - Key management and address derivation
65
- - **RpcClient** - JSON-RPC interaction
66
- - **NonceManager** - Nonce management
67
- - **TxBuilder** - Transaction building
65
+ #### `generate_private_key() => string`
66
+ Generate a new random private key.
68
67
 
69
- ### Constants
68
+ ```javascript
69
+ const privateKey = generate_private_key();
70
+ ```
71
+
72
+ #### `private_key_to_address(privateKey: string) => string`
73
+ Derive address from private key.
74
+
75
+ ```javascript
76
+ const address = private_key_to_address('0x...');
77
+ ```
78
+
79
+ #### `Wallet`
80
+ Wallet class for local transaction signing.
81
+
82
+ ```javascript
83
+ import { Wallet } from '@altiuslabs/tx-sdk';
84
+
85
+ // Create wallet from private key
86
+ const wallet = new Wallet('0x...');
87
+
88
+ // Get address
89
+ console.log(wallet.address); // '0x...'
90
+
91
+ // Sign hash
92
+ const signature = await wallet.sign_hash('0x...');
93
+ ```
94
+
95
+ ---
96
+
97
+ ### Transaction Builder
98
+
99
+ #### `create_transaction() => TxBuilder`
100
+ Create a new transaction builder.
101
+
102
+ ```javascript
103
+ import { create_transaction } from '@altiuslabs/tx-sdk';
104
+
105
+ const tx = create_transaction()
106
+ .chain_id(1)
107
+ .nonce(0)
108
+ .gas_limit(21000)
109
+ .to('0x...')
110
+ .value(0)
111
+ .data('0x...')
112
+ .fee_token('0xa1700000000000000000000000000000000000001')
113
+ .max_fee_per_gas_usd(50000000000);
114
+ ```
115
+
116
+ #### `TxBuilder` Methods
117
+
118
+ | Method | Description |
119
+ |--------|-------------|
120
+ | `.chain_id(id)` | Set chain ID |
121
+ | `.nonce(nonce)` | Set transaction nonce |
122
+ | `.gas_limit(limit)` | Set gas limit |
123
+ | `.to(address)` | Set recipient address |
124
+ | `.value(amount)` | Set transaction value (in wei) |
125
+ | `.data(calldata)` | Set input data |
126
+ | `.max_priority_fee_per_gas(gas)` | Set max priority fee per gas |
127
+ | `.max_fee_per_gas(gas)` | Set max fee per gas |
128
+ | `.fee_token(address)` | Set fee token address |
129
+ | `.fee_payer(address)` | Set fee payer address (optional, defaults to sender) |
130
+ | `.max_fee_per_gas_usd(usd)` | Set max fee per gas in USD attodollars |
131
+ | `.fee_payer_signature(signature)` | Set fee payer signature |
132
+ | `.erc20_transfer(token, to, amount)` | Build ERC20 transfer |
133
+ | `.build()` | Build transaction object |
134
+ | `.signature_hash()` | Compute signature hash |
135
+ | `.sign(wallet)` | Sign transaction with wallet |
136
+
137
+ #### `fee_payer_signature_hash(tx: object, sender: string) => string`
138
+ Calculate the hash that fee_payer should sign.
139
+
140
+ ---
141
+
142
+ ### RPC Client
143
+
144
+ #### `create_rpc_client(url: string) => RpcClient`
145
+ Create an RPC client.
146
+
147
+ ```javascript
148
+ import { create_rpc_client } from '@altiuslabs/tx-sdk';
149
+
150
+ const rpc = create_rpc_client('https://rpc.altius.xyz');
151
+ ```
152
+
153
+ #### `RpcClient` Methods
154
+
155
+ | Method | Description |
156
+ |--------|-------------|
157
+ | `.get_chain_id()` | Get chain ID |
158
+ | `.get_block_number()` | Get latest block number |
159
+ | `.get_balance(address)` | Get native ETH balance |
160
+ | `.get_nonce(address)` | Get transaction count (nonce) |
161
+ | `.get_code(address)` | Get contract code at address |
162
+ | `.call(call_object)` | Execute contract call |
163
+ | `.send_raw_transaction(tx)` | Send signed transaction |
164
+ | `.get_transaction_receipt(hash)` | Get transaction receipt |
165
+ | `.wait_for_receipt(hash, timeout_ms)` | Wait for transaction to be mined |
166
+ | `.get_erc20_balance(token, owner)` | Get ERC20 token balance |
167
+
168
+ ---
169
+
170
+ ### Nonce Manager
171
+
172
+ #### `create_nonce_manager(rpc: RpcClient, address: string) => NonceManager`
173
+ Create a nonce manager.
70
174
 
71
175
  ```javascript
72
- import {
73
- USDA_ADDRESS,
74
- FEE_TOKEN_FACTORY_ADDRESS,
75
- FEE_MANAGER_ADDRESS,
76
- BASE_FEE_ATTO,
77
- DEFAULT_GAS_LIMIT,
78
- } from '@altiuslabs/tx-sdk/constants';
176
+ import { create_nonce_manager } from '@altiuslabs/tx-sdk';
177
+
178
+ const nm = create_nonce_manager(rpc, address);
79
179
  ```
80
180
 
181
+ #### `NonceManager` Methods
182
+
183
+ | Method | Description |
184
+ |--------|-------------|
185
+ | `.get_nonce()` | Get current nonce |
186
+ | `.get_and_increment_nonce()` | Get and increment nonce |
187
+ | `.reset_nonce()` | Reset nonce cache |
188
+
189
+ ---
190
+
191
+ ### High-Level Client
192
+
193
+ #### `create_tx_client(privateKey: string, rpcUrl: string, feeToken: string) => TxClient`
194
+ Create a high-level transaction client.
195
+
196
+ ```javascript
197
+ import { create_tx_client } from '@altiuslabs/tx-sdk';
198
+ import { USDA_ADDRESS } from '@altiuslabs/tx-sdk';
199
+
200
+ const client = create_tx_client(privateKey, 'https://rpc.altius.xyz', USDA_ADDRESS);
201
+ ```
202
+
203
+ #### `TxClientBuilder`
204
+
205
+ ```javascript
206
+ import { TxClientBuilder } from '@altiuslabs/tx-sdk';
207
+
208
+ const client = new TxClientBuilder(privateKey, 'https://rpc.altius.xyz')
209
+ .fee_token(USDA_ADDRESS)
210
+ .base_fee(50000000000)
211
+ .gas_limit(100000)
212
+ .build();
213
+ ```
214
+
215
+ #### `TxClient` Methods
216
+
217
+ | Method | Description |
218
+ |--------|-------------|
219
+ | `.address()` | Get wallet address |
220
+ | `.chain_id()` | Get chain ID |
221
+ | `.get_nonce()` | Get current nonce |
222
+ | `.build_erc20_transfer(token, recipient, amount)` | Build ERC20 transfer |
223
+ | `.sign(tx)` | Sign transaction |
224
+ | `.send(tx)` | Send transaction |
225
+ | `.send_and_wait(tx, timeout_ms)` | Send and wait for receipt |
226
+ | `.send_erc20_transfer(token, recipient, amount)` | Send ERC20 transfer |
227
+ | `.transfer_usda(recipient, amount_micro)` | Transfer USDA (amount in microdollars) |
228
+ | `.fee_token_balance(address)` | Get fee token balance |
229
+ | `.native_balance(address)` | Get native ETH balance |
230
+ | `.reset_nonce()` | Reset nonce cache |
231
+
232
+ ---
233
+
234
+ ### Utilities
235
+
236
+ #### `keccak256(data: string | Buffer) => string`
237
+ Compute keccak256 hash.
238
+
239
+ ```javascript
240
+ import { keccak256 } from '@altiuslabs/tx-sdk';
241
+
242
+ const hash = keccak256('0x...');
243
+ ```
244
+
245
+ #### `pad_hex(hex: string, bytes: number) => string`
246
+ Pad hex string to specified length.
247
+
248
+ ```javascript
249
+ import { pad_hex } from '@altiuslabs/tx-sdk';
250
+
251
+ pad_hex('0x1234', 32); // '0x0000000000000000000000000000000000000000000000000000000000001234'
252
+ ```
253
+
254
+ #### `num_to_hex(num: number, bytes?: number) => string`
255
+ Convert number to padded hex.
256
+
257
+ ```javascript
258
+ import { num_to_hex } from '@altiuslabs/tx-sdk';
259
+
260
+ num_to_hex(123, 32); // '0x000000000000000000000000000000000000000000000000000000000000007b'
261
+ ```
262
+
263
+ #### `rlp_encode(items: array) => string`
264
+ RLP encode an array.
265
+
266
+ ```javascript
267
+ import { rlp_encode } from '@altiuslabs/tx-sdk';
268
+
269
+ const encoded = rlp_encode([1, 2, 3]);
270
+ ```
271
+
272
+ #### `computeFeeTokenAddress(creator: string, salt: string) => { address: string, index: bigint }`
273
+ Compute deterministic fee token address.
274
+
275
+ ```javascript
276
+ import { computeFeeTokenAddress } from '@altiuslabs/tx-sdk';
277
+
278
+ const { address, index } = computeFeeTokenAddress(creator, salt);
279
+ ```
280
+
281
+ #### `is_fee_token_prefix(address: string) => boolean`
282
+ Check if address has fee token prefix (0xa170...).
283
+
284
+ ```javascript
285
+ import { is_fee_token_prefix } from '@altiuslabs/tx-sdk';
286
+
287
+ is_fee_token_prefix('0xa1700000000000000000000000000000000000001'); // true
288
+ ```
289
+
290
+ #### `is_valid_fee_token_address(address: string) => boolean`
291
+ Check if address is valid fee token (has prefix and not zero).
292
+
293
+ ```javascript
294
+ import { is_valid_fee_token_address } from '@altiuslabs/tx-sdk';
295
+
296
+ is_valid_fee_token_address('0xa1700000000000000000000000000000000000001'); // true
297
+ ```
298
+
299
+ #### `is_index_reserved(index: bigint | number) => boolean`
300
+ Check if index is reserved (index < 256).
301
+
302
+ ```javascript
303
+ import { is_index_reserved } from '@altiuslabs/tx-sdk';
304
+
305
+ is_index_reserved(100n); // true
306
+ is_index_reserved(300n); // false
307
+ ```
308
+
309
+ ---
310
+
311
+ ### Constants
312
+
313
+ | Constant | Description |
314
+ |----------|-------------|
315
+ | `USDA_ADDRESS` | USDA Fee Token: `0xa1700000000000000000000000000000000000001` |
316
+ | `FEE_TOKEN_FACTORY_ADDRESS` | Fee Token Factory: `0xa170000000000000000000000000000000000000` |
317
+ | `FEE_MANAGER_ADDRESS` | Fee Manager: `0xFE00000000000000000000000000000000000001` |
318
+ | `ZERO_ADDRESS` | Zero address: `0x0000000000000000000000000000000000000000` |
319
+ | `BASE_FEE_ATTO` | Base fee: 50,000,000,000 (50 G-attodollars/gas) |
320
+ | `DEFAULT_MAX_FEE_PER_GAS` | Default max fee: 100,000,000,000 |
321
+ | `DEFAULT_GAS_LIMIT` | Default gas limit: 100,000 |
322
+ | `FAUCET_AMOUNT_MICRO` | Faucet amount: 100,000,000 microdollars |
323
+ | `FAUCET_AMOUNT_WEI` | Faucet amount: 100,000,000,000,000,000,000 wei |
324
+ | `TRANSFER_AMOUNT_MICRO` | Default transfer: 10,000,000 microdollars |
325
+ | `TRANSFER_AMOUNT_WEI` | Default transfer: 10,000,000,000,000,000,000 wei |
326
+ | `RESERVED_THRESHOLD` | Reserved threshold: 256 |
327
+ | `FAUCET_FEE_TOKEN` | Default faucet fee token (same as USDA_ADDRESS) |
328
+
329
+ ---
330
+
81
331
  ## Security
82
332
 
83
333
  **IMPORTANT**: Private keys never leave the client. All signing happens locally.
@@ -91,6 +341,8 @@ const signedTx = await client.sign(tx);
91
341
  await rpcClient.send('eth_sendRawTransaction', [signedTx.raw_transaction]);
92
342
  ```
93
343
 
344
+ ---
345
+
94
346
  ## Transaction Type
95
347
 
96
348
  This SDK supports the USD Multi-Token fee model (0x7a transaction type):
@@ -99,6 +351,8 @@ This SDK supports the USD Multi-Token fee model (0x7a transaction type):
99
351
  - **fee_payer**: Account paying the fee (optional, defaults to sender)
100
352
  - **max_fee_per_gas_usd_attodollars**: Max gas price in USD attodollars/gas
101
353
 
354
+ ---
355
+
102
356
  ## License
103
357
 
104
358
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@altiuslabs/tx-sdk",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "SDK for signing and sending Altius USD multi-token transactions",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -23,7 +23,7 @@ export class TxBuilder {
23
23
  this._data = '0x';
24
24
  this._max_priority_fee_per_gas = 0;
25
25
  this._max_fee_per_gas = 0;
26
- this._max_fee_per_gas_usd_attodollars = 0;
26
+ this._max_fee_per_gas_usd_attodollars = 40000000000n; // $0.04/gas default
27
27
  this._fee_token = null;
28
28
  this._fee_payer = '0x0000000000000000000000000000000000000000';
29
29
  this._fee_payer_signature = '0x';
@@ -227,9 +227,14 @@ export class TxBuilder {
227
227
  const rBuffer = Buffer.from(sig.r.slice(2), 'hex');
228
228
  const sBuffer = Buffer.from(sig.s.slice(2), 'hex');
229
229
 
230
+ // Note: y_parity is used directly as a number in signature_bytes below
231
+
230
232
  // Build signed transaction list (for EIP-2718 encoding)
231
233
  // Format matches node's rlp_encode_fields: [chainId, nonce, ..., feePayerSignature, signature]
232
234
  // IMPORTANT: Use Buffer for addresses and data to match Rust alloy-rlp encoding
235
+ //
236
+ // NOTE: For signature encoding, we use RLP encoding to match Rust alloy's behavior.
237
+ // The signature (y_parity, r, s) is added to the RLP list and encoded together.
233
238
  const signed_fields = [
234
239
  BigInt(tx.chain_id),
235
240
  BigInt(tx.nonce),
@@ -244,14 +249,15 @@ export class TxBuilder {
244
249
  Buffer.from(feePayer.slice(2), 'hex'), // fee_payer as buffer
245
250
  BigInt(tx.max_fee_per_gas_usd_attodollars),
246
251
  Buffer.from(tx.fee_payer_signature?.slice(2) || '', 'hex') || Buffer.alloc(0),
247
- // Signature: y_parity (1 byte), r (32 bytes), s (32 bytes)
248
- y_parity,
249
- rBuffer,
250
- sBuffer,
252
+ // Signature: y_parity, r, s - RLP encoded as part of the list
253
+ BigInt(y_parity),
254
+ Buffer.from(sig.r.slice(2), 'hex'), // r as 32-byte buffer
255
+ Buffer.from(sig.s.slice(2), 'hex'), // s as 32-byte buffer
251
256
  ];
252
257
 
253
258
  // EIP-2718: first byte is type (0x7a), then RLP encoded fields
254
259
  const rlp_fields_buf = RLP.encode(signed_fields);
260
+
255
261
  const raw_transaction = '0x7a' + rlp_fields_buf.toString('hex');
256
262
 
257
263
  const transaction_hash = keccak256(raw_transaction);
package/src/wallet.js CHANGED
@@ -23,9 +23,12 @@ export function generate_private_key() {
23
23
  */
24
24
  export function private_key_to_address(private_key) {
25
25
  const pk = private_key.slice(2);
26
- const publicKey = secp256k1.getPublicKey(pk, true);
27
- // Remove first byte (prefix) and take last 64 bytes (x, y)
28
- const hash = keccak256(publicKey.slice(1));
26
+ // false = uncompressed key (65 bytes starting with 0x04)
27
+ const publicKey = secp256k1.getPublicKey(pk, false);
28
+ // Convert hex string to Buffer
29
+ const publicKeyBuffer = Buffer.from(publicKey, 'hex');
30
+ // Remove first byte (prefix 0x04 for uncompressed key) and hash
31
+ const hash = keccak256(publicKeyBuffer.slice(1));
29
32
  // Take last 20 bytes
30
33
  return '0x' + hash.slice(-40);
31
34
  }
@@ -56,7 +59,13 @@ function parse_der(der) {
56
59
  offset += 2;
57
60
 
58
61
  // Get r (may need padding for leading zeros)
59
- const rHex = der.slice(offset, offset + rLen * 2);
62
+ // DER uses signed integers, so if MSB >= 0x80, a 0x00 prefix is added
63
+ // We need to strip that prefix if present, then pad to 32 bytes
64
+ let rHex = der.slice(offset, offset + rLen * 2);
65
+ if (rHex.startsWith('00') && rLen === 33) {
66
+ // Strip the leading 0x00 added by DER encoding
67
+ rHex = rHex.slice(2);
68
+ }
60
69
  const r = '0x' + rHex.padStart(64, '0');
61
70
  offset += rLen * 2;
62
71
 
@@ -68,8 +77,12 @@ function parse_der(der) {
68
77
  const sLen = parseInt(der.slice(offset, offset + 2), 16);
69
78
  offset += 2;
70
79
 
71
- // Get s
72
- const sHex = der.slice(offset, offset + sLen * 2);
80
+ // Get s (may need to strip leading 0x00 added by DER encoding)
81
+ let sHex = der.slice(offset, offset + sLen * 2);
82
+ if (sHex.startsWith('00') && sLen === 33) {
83
+ // Strip the leading 0x00 added by DER encoding
84
+ sHex = sHex.slice(2);
85
+ }
73
86
  const s = '0x' + sHex.padStart(64, '0');
74
87
 
75
88
  return { r, s };