@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 +269 -15
- package/package.json +1 -1
- package/src/transaction.js +11 -5
- package/src/wallet.js +19 -6
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 '@
|
|
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
|
-
###
|
|
63
|
+
### Wallet & Signing
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
package/src/transaction.js
CHANGED
|
@@ -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
|
|
248
|
-
y_parity,
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|