@altiuslabs/tx-sdk 0.1.21 → 0.1.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@altiuslabs/tx-sdk",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "SDK for signing and sending Altius USD multi-token transactions",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -23,10 +23,10 @@ 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
- this._fee_payer = '0x0000000000000000000000000000000000000000';
29
- this._fee_payer_signature = '0x';
28
+ this._fee_payer = null;
29
+ this._fee_payer_signature = null;
30
30
  }
31
31
 
32
32
  /**
@@ -158,6 +158,9 @@ export class TxBuilder {
158
158
  if (this._nonce === null) throw new Error('nonce is required');
159
159
  if (!this._fee_token) throw new Error('fee_token is required');
160
160
 
161
+ // Default fee_payer to zero address (sender-pays)
162
+ const fee_payer = this._fee_payer || '0x0000000000000000000000000000000000000000';
163
+
161
164
  return {
162
165
  chain_id: this._chain_id,
163
166
  nonce: this._nonce,
@@ -169,7 +172,7 @@ export class TxBuilder {
169
172
  max_fee_per_gas: this._max_fee_per_gas,
170
173
  max_fee_per_gas_usd_attodollars: this._max_fee_per_gas_usd_attodollars,
171
174
  fee_token: this._fee_token,
172
- fee_payer: this._fee_payer || '0x0000000000000000000000000000000000000000',
175
+ fee_payer,
173
176
  fee_payer_signature: this._fee_payer_signature,
174
177
  };
175
178
  }
@@ -220,16 +223,33 @@ export class TxBuilder {
220
223
 
221
224
  const tx = this.build();
222
225
 
223
- // Use sender as fee_payer if not specified
224
- const feePayer = tx.fee_payer || wallet.address;
226
+ // Use sender as fee_payer if not specified (null means use sender)
227
+ const feePayer = tx.fee_payer === '0x0000000000000000000000000000000000000000' || !tx.fee_payer
228
+ ? wallet.address
229
+ : tx.fee_payer;
230
+
231
+ // Auto-generate fee_payer_signature if not set and fee_payer equals sender
232
+ let feePayerSignature = tx.fee_payer_signature;
233
+ if (!feePayerSignature && feePayer.toLowerCase() === wallet.address.toLowerCase()) {
234
+ // Compute fee_payer signature hash using sender as the sender
235
+ const feePayerSigHash = fee_payer_signature_hash(tx, wallet.address);
236
+ const feePayerSig = await wallet.sign_hash(feePayerSigHash);
237
+ // Format: 0x + v (1 byte) + r (32 bytes) + s (32 bytes)
238
+ feePayerSignature = '0x' + feePayerSig.v.toString(16).padStart(2, '0') + feePayerSig.r.slice(2) + feePayerSig.s.slice(2);
239
+ }
225
240
 
226
241
  // Convert r, s to 32-byte buffers
227
242
  const rBuffer = Buffer.from(sig.r.slice(2), 'hex');
228
243
  const sBuffer = Buffer.from(sig.s.slice(2), 'hex');
229
244
 
245
+ // Note: y_parity is used directly as a number in signature_bytes below
246
+
230
247
  // Build signed transaction list (for EIP-2718 encoding)
231
248
  // Format matches node's rlp_encode_fields: [chainId, nonce, ..., feePayerSignature, signature]
232
249
  // IMPORTANT: Use Buffer for addresses and data to match Rust alloy-rlp encoding
250
+ //
251
+ // NOTE: For signature encoding, we use RLP encoding to match Rust alloy's behavior.
252
+ // The signature (y_parity, r, s) is added to the RLP list and encoded together.
233
253
  const signed_fields = [
234
254
  BigInt(tx.chain_id),
235
255
  BigInt(tx.nonce),
@@ -243,15 +263,16 @@ export class TxBuilder {
243
263
  Buffer.from(tx.fee_token.slice(2), 'hex'), // fee_token as buffer
244
264
  Buffer.from(feePayer.slice(2), 'hex'), // fee_payer as buffer
245
265
  BigInt(tx.max_fee_per_gas_usd_attodollars),
246
- 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,
266
+ Buffer.from(feePayerSignature?.slice(2) || '', 'hex') || Buffer.alloc(0),
267
+ // Signature: y_parity, r, s - RLP encoded as part of the list
268
+ BigInt(y_parity),
269
+ Buffer.from(sig.r.slice(2), 'hex'), // r as 32-byte buffer
270
+ Buffer.from(sig.s.slice(2), 'hex'), // s as 32-byte buffer
251
271
  ];
252
272
 
253
273
  // EIP-2718: first byte is type (0x7a), then RLP encoded fields
254
274
  const rlp_fields_buf = RLP.encode(signed_fields);
275
+
255
276
  const raw_transaction = '0x7a' + rlp_fields_buf.toString('hex');
256
277
 
257
278
  const transaction_hash = keccak256(raw_transaction);
@@ -259,6 +280,7 @@ export class TxBuilder {
259
280
  return {
260
281
  ...tx,
261
282
  fee_payer: feePayer,
283
+ fee_payer_signature: feePayerSignature,
262
284
  v: y_parity,
263
285
  r: sig.r,
264
286
  s: sig.s,
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 };