@sequence0/sdk 2.0.1 → 2.1.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.
Files changed (73) hide show
  1. package/README.md +10 -10
  2. package/dist/chains/casper.d.ts +74 -0
  3. package/dist/chains/casper.d.ts.map +1 -0
  4. package/dist/chains/casper.js +512 -0
  5. package/dist/chains/casper.js.map +1 -0
  6. package/dist/chains/cosmos.d.ts +22 -0
  7. package/dist/chains/cosmos.d.ts.map +1 -1
  8. package/dist/chains/cosmos.js +113 -12
  9. package/dist/chains/cosmos.js.map +1 -1
  10. package/dist/chains/ethereum.d.ts.map +1 -1
  11. package/dist/chains/ethereum.js +14 -2
  12. package/dist/chains/ethereum.js.map +1 -1
  13. package/dist/chains/flow.d.ts +57 -0
  14. package/dist/chains/flow.d.ts.map +1 -0
  15. package/dist/chains/flow.js +435 -0
  16. package/dist/chains/flow.js.map +1 -0
  17. package/dist/chains/icp.d.ts.map +1 -1
  18. package/dist/chains/icp.js +483 -67
  19. package/dist/chains/icp.js.map +1 -1
  20. package/dist/chains/iota.d.ts +80 -0
  21. package/dist/chains/iota.d.ts.map +1 -0
  22. package/dist/chains/iota.js +502 -0
  23. package/dist/chains/iota.js.map +1 -0
  24. package/dist/chains/kadena.d.ts +81 -0
  25. package/dist/chains/kadena.d.ts.map +1 -0
  26. package/dist/chains/kadena.js +356 -0
  27. package/dist/chains/kadena.js.map +1 -0
  28. package/dist/chains/near.d.ts +4 -1
  29. package/dist/chains/near.d.ts.map +1 -1
  30. package/dist/chains/near.js +58 -15
  31. package/dist/chains/near.js.map +1 -1
  32. package/dist/chains/nervos.d.ts +148 -0
  33. package/dist/chains/nervos.d.ts.map +1 -0
  34. package/dist/chains/nervos.js +913 -0
  35. package/dist/chains/nervos.js.map +1 -0
  36. package/dist/chains/radix.d.ts +81 -0
  37. package/dist/chains/radix.d.ts.map +1 -0
  38. package/dist/chains/radix.js +289 -0
  39. package/dist/chains/radix.js.map +1 -0
  40. package/dist/chains/solana.d.ts +4 -0
  41. package/dist/chains/solana.d.ts.map +1 -1
  42. package/dist/chains/solana.js +47 -13
  43. package/dist/chains/solana.js.map +1 -1
  44. package/dist/chains/stacks.d.ts +113 -0
  45. package/dist/chains/stacks.d.ts.map +1 -0
  46. package/dist/chains/stacks.js +576 -0
  47. package/dist/chains/stacks.js.map +1 -0
  48. package/dist/chains/sui.d.ts +11 -0
  49. package/dist/chains/sui.d.ts.map +1 -1
  50. package/dist/chains/sui.js +49 -8
  51. package/dist/chains/sui.js.map +1 -1
  52. package/dist/core/client.js +1 -1
  53. package/dist/core/client.js.map +1 -1
  54. package/dist/core/solvency.d.ts +1 -1
  55. package/dist/core/solvency.js +1 -1
  56. package/dist/core/types.d.ts +94 -2
  57. package/dist/core/types.d.ts.map +1 -1
  58. package/dist/core/universal-account.d.ts +1 -1
  59. package/dist/core/universal-account.js +1 -1
  60. package/dist/core/witness.d.ts +1 -1
  61. package/dist/core/witness.js +1 -1
  62. package/dist/settlement/settlement.d.ts +1 -1
  63. package/dist/settlement/settlement.js +1 -1
  64. package/dist/utils/discovery.d.ts.map +1 -1
  65. package/dist/utils/discovery.js +51 -4
  66. package/dist/utils/discovery.js.map +1 -1
  67. package/dist/utils/http.d.ts +1 -1
  68. package/dist/utils/http.js +1 -1
  69. package/dist/wallet/wallet.d.ts +11 -1
  70. package/dist/wallet/wallet.d.ts.map +1 -1
  71. package/dist/wallet/wallet.js +60 -2
  72. package/dist/wallet/wallet.js.map +1 -1
  73. package/package.json +1 -1
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Stacks (STX) Chain Adapter
3
+ *
4
+ * Builds Stacks SIP-005 token transfer transactions, attaches secp256k1 ECDSA
5
+ * signatures from the FROST threshold signing network, and broadcasts via
6
+ * Stacks node HTTP API.
7
+ *
8
+ * Uses @stacks/transactions if available, falls back to manual serialization
9
+ * using native fetch and Node.js crypto (no external deps required).
10
+ *
11
+ * Stacks uses secp256k1 (same curve as Bitcoin/Ethereum).
12
+ *
13
+ * Optional: npm install @stacks/transactions
14
+ */
15
+ import { ChainAdapter, StacksTransaction } from '../core/types';
16
+ export declare class StacksAdapter implements ChainAdapter {
17
+ private rpcUrl;
18
+ private network;
19
+ private stacksLib;
20
+ private useNativeLib;
21
+ constructor(network?: 'mainnet' | 'testnet', rpcUrl?: string);
22
+ getRpcUrl(): string;
23
+ /**
24
+ * Build an unsigned Stacks token transfer transaction (SIP-005).
25
+ *
26
+ * The returned hex string encodes a JSON payload containing:
27
+ * - signingHash: SHA-512/256 digest of the transaction with auth cleared
28
+ * - serializedTx: hex-encoded unsigned transaction bytes
29
+ * - nonce, fee, and other metadata needed for signature attachment
30
+ */
31
+ buildTransaction(tx: StacksTransaction, fromAddress: string): Promise<string>;
32
+ getSigningPayload(unsignedTx: string): string;
33
+ /**
34
+ * Attach a secp256k1 ECDSA signature to the Stacks transaction.
35
+ *
36
+ * Stacks requires a 65-byte recoverable signature in the auth spending condition:
37
+ * [recovery_id (1 byte)] [r (32 bytes)] [s (32 bytes)]
38
+ *
39
+ * The FROST signature is expected as 65 bytes: r (32) + s (32) + v (1),
40
+ * matching the EVM convention. We reorder to Stacks format.
41
+ */
42
+ attachSignature(unsignedTx: string, signature: string): Promise<string>;
43
+ /**
44
+ * Broadcast a signed Stacks transaction via /v2/transactions
45
+ */
46
+ broadcast(signedTx: string): Promise<string>;
47
+ /**
48
+ * Get STX balance in microSTX
49
+ */
50
+ getBalance(address: string): Promise<string>;
51
+ private buildWithLib;
52
+ private attachWithLib;
53
+ private buildManual;
54
+ private attachManual;
55
+ /**
56
+ * Serialize a standard single-sig authorization (P2PKH).
57
+ *
58
+ * Layout:
59
+ * auth_type (1) + hash_mode (1) + signer_hash160 (20) + nonce (8) + fee (8)
60
+ * + key_encoding (1) + signature (65, zeroed for unsigned)
61
+ */
62
+ private serializeAuth;
63
+ /**
64
+ * Serialize token transfer payload.
65
+ *
66
+ * Layout:
67
+ * payload_type (1) + recipient_principal + amount (8) + memo_length_type (1) + memo (34)
68
+ */
69
+ private serializeTokenTransferPayload;
70
+ /**
71
+ * Serialize a Stacks principal address.
72
+ *
73
+ * Standard principal: type_id (1) + version (1) + hash160 (20)
74
+ * Contract principal: type_id (1) + version (1) + hash160 (20) + name_length (1) + name
75
+ */
76
+ private serializePrincipal;
77
+ /**
78
+ * Compute the sighash for a Stacks transaction.
79
+ *
80
+ * The sighash is SHA-512/256 of the serialized transaction with the
81
+ * auth signature field cleared (set to zeros). This is the digest
82
+ * that needs to be signed by the secp256k1 key.
83
+ *
84
+ * Per SIP-005, the initial sighash is computed over the transaction
85
+ * with auth cleared, then wrapped:
86
+ * sighash = SHA-512/256(sighash_presign + auth_type + fee + nonce + key_encoding)
87
+ *
88
+ * For simplicity, we compute the presign sighash (SHA-512/256 of the
89
+ * unsigned serialized tx), which is what the auth spending condition signs.
90
+ */
91
+ private computeSighash;
92
+ /**
93
+ * Embed a recoverable signature into the serialized transaction.
94
+ *
95
+ * The auth section layout (starting after version + chain_id = offset 5):
96
+ * auth_type (1) + hash_mode (1) + signer_hash160 (20) + nonce (8) + fee (8)
97
+ * + key_encoding (1) + signature (65)
98
+ *
99
+ * Signature starts at offset 5 + 1 + 1 + 20 + 8 + 8 + 1 = offset 44.
100
+ * Stacks signature format: [recovery_id (1)] [r (32)] [s (32)] = 65 bytes.
101
+ */
102
+ private embedSignatureInSerialized;
103
+ /**
104
+ * Parse a 65-byte signature from FROST (r[32] + s[32] + v[1])
105
+ * into Stacks format (recovery_id, r, s).
106
+ */
107
+ private parseSignature;
108
+ private fetchNonce;
109
+ private estimateFee;
110
+ }
111
+ export declare function createStacksAdapter(rpcUrl?: string): StacksAdapter;
112
+ export declare function createStacksTestnetAdapter(rpcUrl?: string): StacksAdapter;
113
+ //# sourceMappingURL=stacks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stacks.d.ts","sourceRoot":"","sources":["../../src/chains/stacks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AA+BhE,qBAAa,aAAc,YAAW,YAAY;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,YAAY,CAAU;gBAElB,OAAO,GAAE,SAAS,GAAG,SAAqB,EAAE,MAAM,CAAC,EAAE,MAAM;IAcvE,SAAS,IAAI,MAAM;IAInB;;;;;;;OAOG;IACG,gBAAgB,CAAC,EAAE,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA2BnF,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAS7C;;;;;;;;OAQG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB7E;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BlD;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAiBpC,YAAY;IAyD1B,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,WAAW;IAsDnB,OAAO,CAAC,YAAY;IAmBpB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAarB;;;;;OAKG;IACH,OAAO,CAAC,6BAA6B;IAsBrC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,cAAc;IAOtB;;;;;;;;;OASG;IACH,OAAO,CAAC,0BAA0B;IAgBlC;;;OAGG;IACH,OAAO,CAAC,cAAc;YA2BR,UAAU;YASV,WAAW;CAc5B;AAsHD,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAElE;AAED,wBAAgB,0BAA0B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAEzE"}
@@ -0,0 +1,576 @@
1
+ "use strict";
2
+ /**
3
+ * Stacks (STX) Chain Adapter
4
+ *
5
+ * Builds Stacks SIP-005 token transfer transactions, attaches secp256k1 ECDSA
6
+ * signatures from the FROST threshold signing network, and broadcasts via
7
+ * Stacks node HTTP API.
8
+ *
9
+ * Uses @stacks/transactions if available, falls back to manual serialization
10
+ * using native fetch and Node.js crypto (no external deps required).
11
+ *
12
+ * Stacks uses secp256k1 (same curve as Bitcoin/Ethereum).
13
+ *
14
+ * Optional: npm install @stacks/transactions
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.StacksAdapter = void 0;
51
+ exports.createStacksAdapter = createStacksAdapter;
52
+ exports.createStacksTestnetAdapter = createStacksTestnetAdapter;
53
+ const errors_1 = require("../utils/errors");
54
+ const crypto = __importStar(require("crypto"));
55
+ const DEFAULT_RPCS = {
56
+ 'mainnet': 'https://stacks-node-api.mainnet.stacks.co',
57
+ 'testnet': 'https://stacks-node-api.testnet.stacks.co',
58
+ };
59
+ // Stacks transaction versions (SIP-005)
60
+ const TX_VERSION_MAINNET = 0x00;
61
+ const TX_VERSION_TESTNET = 0x80;
62
+ // Network chain IDs
63
+ const CHAIN_ID_MAINNET = 0x00000001;
64
+ const CHAIN_ID_TESTNET = 0x80000000;
65
+ // Auth types and flags
66
+ const AUTH_TYPE_STANDARD = 0x04;
67
+ const PUBKEY_ENCODING_COMPRESSED = 0x00;
68
+ const SINGLE_SIG_SPENDING_CONDITION = 0x00; // hash mode: P2PKH
69
+ // Payload type for token transfer
70
+ const PAYLOAD_TYPE_TOKEN_TRANSFER = 0x00;
71
+ // Post-condition mode: allow
72
+ const POST_CONDITION_MODE_ALLOW = 0x01;
73
+ // Anchor mode
74
+ const ANCHOR_MODE_ANY = 0x03;
75
+ class StacksAdapter {
76
+ constructor(network = 'mainnet', rpcUrl) {
77
+ this.network = network;
78
+ this.rpcUrl = rpcUrl || DEFAULT_RPCS[network];
79
+ this.useNativeLib = false;
80
+ try {
81
+ this.stacksLib = require('@stacks/transactions');
82
+ this.useNativeLib = true;
83
+ }
84
+ catch {
85
+ // Fall back to manual serialization
86
+ this.stacksLib = null;
87
+ }
88
+ }
89
+ getRpcUrl() {
90
+ return this.rpcUrl;
91
+ }
92
+ /**
93
+ * Build an unsigned Stacks token transfer transaction (SIP-005).
94
+ *
95
+ * The returned hex string encodes a JSON payload containing:
96
+ * - signingHash: SHA-512/256 digest of the transaction with auth cleared
97
+ * - serializedTx: hex-encoded unsigned transaction bytes
98
+ * - nonce, fee, and other metadata needed for signature attachment
99
+ */
100
+ async buildTransaction(tx, fromAddress) {
101
+ try {
102
+ // Fetch nonce if not provided
103
+ const nonce = tx.nonce !== undefined
104
+ ? tx.nonce
105
+ : await this.fetchNonce(fromAddress);
106
+ // Fetch fee estimate if not provided
107
+ const fee = tx.fee
108
+ ? BigInt(tx.fee)
109
+ : await this.estimateFee();
110
+ const amount = BigInt(tx.amount);
111
+ if (this.useNativeLib) {
112
+ return await this.buildWithLib(tx, fromAddress, nonce, fee, amount);
113
+ }
114
+ return this.buildManual(tx, fromAddress, nonce, fee, amount);
115
+ }
116
+ catch (e) {
117
+ if (e instanceof errors_1.ChainError)
118
+ throw e;
119
+ throw new errors_1.ChainError(`Failed to build Stacks transaction: ${e.message}`, 'stacks');
120
+ }
121
+ }
122
+ getSigningPayload(unsignedTx) {
123
+ try {
124
+ const payload = JSON.parse(Buffer.from(unsignedTx, 'hex').toString());
125
+ return payload.signingHash;
126
+ }
127
+ catch {
128
+ return unsignedTx;
129
+ }
130
+ }
131
+ /**
132
+ * Attach a secp256k1 ECDSA signature to the Stacks transaction.
133
+ *
134
+ * Stacks requires a 65-byte recoverable signature in the auth spending condition:
135
+ * [recovery_id (1 byte)] [r (32 bytes)] [s (32 bytes)]
136
+ *
137
+ * The FROST signature is expected as 65 bytes: r (32) + s (32) + v (1),
138
+ * matching the EVM convention. We reorder to Stacks format.
139
+ */
140
+ async attachSignature(unsignedTx, signature) {
141
+ try {
142
+ const payload = JSON.parse(Buffer.from(unsignedTx, 'hex').toString());
143
+ const sig = signature.startsWith('0x') ? signature.slice(2) : signature;
144
+ if (this.useNativeLib) {
145
+ return this.attachWithLib(payload, sig);
146
+ }
147
+ return this.attachManual(payload, sig);
148
+ }
149
+ catch (e) {
150
+ throw new errors_1.ChainError(`Failed to attach Stacks signature: ${e.message}`, 'stacks');
151
+ }
152
+ }
153
+ /**
154
+ * Broadcast a signed Stacks transaction via /v2/transactions
155
+ */
156
+ async broadcast(signedTx) {
157
+ try {
158
+ const payload = JSON.parse(Buffer.from(signedTx, 'hex').toString());
159
+ const txHex = payload.signedTx || payload.serializedTx;
160
+ const response = await fetch(`${this.rpcUrl}/v2/transactions`, {
161
+ method: 'POST',
162
+ headers: { 'Content-Type': 'application/octet-stream' },
163
+ body: Buffer.from(txHex, 'hex'),
164
+ });
165
+ if (!response.ok) {
166
+ const errorText = await response.text();
167
+ throw new Error(`Broadcast failed (${response.status}): ${errorText}`);
168
+ }
169
+ // The API returns the txid as a JSON string (quoted hex)
170
+ const txId = await response.text();
171
+ return txId.replace(/^"|"$/g, '');
172
+ }
173
+ catch (e) {
174
+ if (e instanceof errors_1.ChainError)
175
+ throw e;
176
+ throw new errors_1.ChainError(`Failed to broadcast Stacks tx: ${e.message}`, 'stacks');
177
+ }
178
+ }
179
+ /**
180
+ * Get STX balance in microSTX
181
+ */
182
+ async getBalance(address) {
183
+ try {
184
+ const response = await fetch(`${this.rpcUrl}/v2/accounts/${address}?proof=0`);
185
+ if (!response.ok)
186
+ return '0';
187
+ const data = await response.json();
188
+ // balance is returned as hex string prefixed with 0x
189
+ const balanceHex = data.balance;
190
+ return BigInt(balanceHex).toString();
191
+ }
192
+ catch {
193
+ return '0';
194
+ }
195
+ }
196
+ // ────────────────────────────────────────────────
197
+ // Private: @stacks/transactions library path
198
+ // ────────────────────────────────────────────────
199
+ async buildWithLib(tx, fromAddress, nonce, fee, amount) {
200
+ const { makeUnsignedSTXTokenTransfer, createStacksPrivateKey, serializeTransaction, TransactionVersion, AnchorMode, } = this.stacksLib;
201
+ // Build with a placeholder public key; we only need the structure
202
+ const txOptions = {
203
+ recipient: tx.to,
204
+ amount: amount,
205
+ fee: fee,
206
+ nonce: nonce,
207
+ anchorMode: AnchorMode?.Any ?? 3,
208
+ network: this.network,
209
+ };
210
+ if (tx.memo) {
211
+ txOptions.memo = tx.memo;
212
+ }
213
+ if (tx.senderPublicKey) {
214
+ txOptions.publicKey = tx.senderPublicKey;
215
+ }
216
+ const transaction = await makeUnsignedSTXTokenTransfer(txOptions);
217
+ // Serialize unsigned transaction
218
+ const serialized = Buffer.from(serializeTransaction
219
+ ? serializeTransaction(transaction)
220
+ : transaction.serialize());
221
+ // Compute sighash: the digest that needs signing
222
+ // For Stacks, this is SHA-512/256 of the transaction with cleared auth
223
+ const sighash = this.computeSighash(serialized);
224
+ const payload = {
225
+ signingHash: sighash,
226
+ serializedTx: serialized.toString('hex'),
227
+ nonce,
228
+ fee: fee.toString(),
229
+ useLib: true,
230
+ };
231
+ return Buffer.from(JSON.stringify(payload)).toString('hex');
232
+ }
233
+ attachWithLib(payload, sig) {
234
+ // Parse r, s, v from the FROST signature
235
+ const { recoveryId, r, s } = this.parseSignature(sig);
236
+ const serializedTx = Buffer.from(payload.serializedTx, 'hex');
237
+ // The auth field in a single-sig spending condition has a fixed layout.
238
+ // Find the signature slot and write recovery_id + r + s.
239
+ const signedTx = this.embedSignatureInSerialized(serializedTx, recoveryId, r, s);
240
+ const signedPayload = {
241
+ ...payload,
242
+ signedTx: signedTx.toString('hex'),
243
+ signature: sig,
244
+ };
245
+ return Buffer.from(JSON.stringify(signedPayload)).toString('hex');
246
+ }
247
+ // ────────────────────────────────────────────────
248
+ // Private: Manual serialization path (no deps)
249
+ // ────────────────────────────────────────────────
250
+ buildManual(tx, _fromAddress, nonce, fee, amount) {
251
+ const isMainnet = this.network === 'mainnet';
252
+ const txVersion = isMainnet ? TX_VERSION_MAINNET : TX_VERSION_TESTNET;
253
+ const chainId = isMainnet ? CHAIN_ID_MAINNET : CHAIN_ID_TESTNET;
254
+ // Sender public key (33 bytes compressed)
255
+ const senderPubKey = tx.senderPublicKey
256
+ ? Buffer.from(tx.senderPublicKey, 'hex')
257
+ : Buffer.alloc(33);
258
+ // Hash the public key for the spending condition (Hash160 = RIPEMD160(SHA256(pubkey)))
259
+ const pubkeyHash = hash160(senderPubKey);
260
+ // Build the authorization (standard single-sig, P2PKH)
261
+ const auth = this.serializeAuth(pubkeyHash, nonce, fee);
262
+ // Build the payload (token transfer)
263
+ const tokenPayload = this.serializeTokenTransferPayload(tx.to, amount, tx.memo);
264
+ // Assemble the full transaction:
265
+ // version (1) + chain_id (4) + auth + anchor_mode (1) + post_condition_mode (1)
266
+ // + post_conditions_length (4, 0) + payload
267
+ const parts = [
268
+ Buffer.from([txVersion]),
269
+ writeUint32BE(chainId),
270
+ auth,
271
+ Buffer.from([ANCHOR_MODE_ANY]),
272
+ Buffer.from([POST_CONDITION_MODE_ALLOW]),
273
+ writeUint32BE(0), // post-conditions: empty list (length 0)
274
+ tokenPayload,
275
+ ];
276
+ const serialized = Buffer.concat(parts);
277
+ // Compute sighash
278
+ const sighash = this.computeSighash(serialized);
279
+ const payload = {
280
+ signingHash: sighash,
281
+ serializedTx: serialized.toString('hex'),
282
+ nonce,
283
+ fee: fee.toString(),
284
+ useLib: false,
285
+ };
286
+ return Buffer.from(JSON.stringify(payload)).toString('hex');
287
+ }
288
+ attachManual(payload, sig) {
289
+ const { recoveryId, r, s } = this.parseSignature(sig);
290
+ const serializedTx = Buffer.from(payload.serializedTx, 'hex');
291
+ const signedTx = this.embedSignatureInSerialized(serializedTx, recoveryId, r, s);
292
+ const signedPayload = {
293
+ ...payload,
294
+ signedTx: signedTx.toString('hex'),
295
+ signature: sig,
296
+ };
297
+ return Buffer.from(JSON.stringify(signedPayload)).toString('hex');
298
+ }
299
+ // ────────────────────────────────────────────────
300
+ // Private: Serialization helpers
301
+ // ────────────────────────────────────────────────
302
+ /**
303
+ * Serialize a standard single-sig authorization (P2PKH).
304
+ *
305
+ * Layout:
306
+ * auth_type (1) + hash_mode (1) + signer_hash160 (20) + nonce (8) + fee (8)
307
+ * + key_encoding (1) + signature (65, zeroed for unsigned)
308
+ */
309
+ serializeAuth(signerHash160, nonce, fee) {
310
+ const parts = [
311
+ Buffer.from([AUTH_TYPE_STANDARD]),
312
+ Buffer.from([SINGLE_SIG_SPENDING_CONDITION]),
313
+ signerHash160,
314
+ writeUint64BE(BigInt(nonce)),
315
+ writeUint64BE(fee),
316
+ Buffer.from([PUBKEY_ENCODING_COMPRESSED]),
317
+ Buffer.alloc(65), // empty signature placeholder
318
+ ];
319
+ return Buffer.concat(parts);
320
+ }
321
+ /**
322
+ * Serialize token transfer payload.
323
+ *
324
+ * Layout:
325
+ * payload_type (1) + recipient_principal + amount (8) + memo_length_type (1) + memo (34)
326
+ */
327
+ serializeTokenTransferPayload(recipient, amount, memo) {
328
+ const parts = [
329
+ Buffer.from([PAYLOAD_TYPE_TOKEN_TRANSFER]),
330
+ this.serializePrincipal(recipient),
331
+ writeUint64BE(amount),
332
+ ];
333
+ // Memo: always 34 bytes, padded with zeros
334
+ const memoBytes = Buffer.alloc(34);
335
+ if (memo) {
336
+ const encoded = Buffer.from(memo, 'utf-8');
337
+ encoded.copy(memoBytes, 0, 0, Math.min(encoded.length, 34));
338
+ }
339
+ parts.push(memoBytes);
340
+ return Buffer.concat(parts);
341
+ }
342
+ /**
343
+ * Serialize a Stacks principal address.
344
+ *
345
+ * Standard principal: type_id (1) + version (1) + hash160 (20)
346
+ * Contract principal: type_id (1) + version (1) + hash160 (20) + name_length (1) + name
347
+ */
348
+ serializePrincipal(address) {
349
+ // Check if this is a contract principal (contains '.')
350
+ const parts = address.split('.');
351
+ // Decode c32check address to get version + hash160
352
+ const { version, hash160: addrHash } = decodec32Address(parts[0]);
353
+ if (parts.length === 2) {
354
+ // Contract principal
355
+ const contractName = Buffer.from(parts[1], 'ascii');
356
+ return Buffer.concat([
357
+ Buffer.from([0x06]), // type: contract principal
358
+ Buffer.from([version]),
359
+ addrHash,
360
+ Buffer.from([contractName.length]),
361
+ contractName,
362
+ ]);
363
+ }
364
+ // Standard principal
365
+ return Buffer.concat([
366
+ Buffer.from([0x05]), // type: standard principal
367
+ Buffer.from([version]),
368
+ addrHash,
369
+ ]);
370
+ }
371
+ /**
372
+ * Compute the sighash for a Stacks transaction.
373
+ *
374
+ * The sighash is SHA-512/256 of the serialized transaction with the
375
+ * auth signature field cleared (set to zeros). This is the digest
376
+ * that needs to be signed by the secp256k1 key.
377
+ *
378
+ * Per SIP-005, the initial sighash is computed over the transaction
379
+ * with auth cleared, then wrapped:
380
+ * sighash = SHA-512/256(sighash_presign + auth_type + fee + nonce + key_encoding)
381
+ *
382
+ * For simplicity, we compute the presign sighash (SHA-512/256 of the
383
+ * unsigned serialized tx), which is what the auth spending condition signs.
384
+ */
385
+ computeSighash(serialized) {
386
+ const hash = crypto.createHash('sha512-256')
387
+ .update(serialized)
388
+ .digest();
389
+ return hash.toString('hex');
390
+ }
391
+ /**
392
+ * Embed a recoverable signature into the serialized transaction.
393
+ *
394
+ * The auth section layout (starting after version + chain_id = offset 5):
395
+ * auth_type (1) + hash_mode (1) + signer_hash160 (20) + nonce (8) + fee (8)
396
+ * + key_encoding (1) + signature (65)
397
+ *
398
+ * Signature starts at offset 5 + 1 + 1 + 20 + 8 + 8 + 1 = offset 44.
399
+ * Stacks signature format: [recovery_id (1)] [r (32)] [s (32)] = 65 bytes.
400
+ */
401
+ embedSignatureInSerialized(serialized, recoveryId, r, s) {
402
+ const signed = Buffer.from(serialized);
403
+ const sigOffset = 5 + 1 + 1 + 20 + 8 + 8 + 1; // = 44
404
+ signed[sigOffset] = recoveryId;
405
+ r.copy(signed, sigOffset + 1);
406
+ s.copy(signed, sigOffset + 33);
407
+ return signed;
408
+ }
409
+ /**
410
+ * Parse a 65-byte signature from FROST (r[32] + s[32] + v[1])
411
+ * into Stacks format (recovery_id, r, s).
412
+ */
413
+ parseSignature(sig) {
414
+ const sigBuf = Buffer.from(sig, 'hex');
415
+ if (sigBuf.length === 65) {
416
+ // Format: r(32) + s(32) + v(1) — EVM convention from FROST
417
+ const r = sigBuf.subarray(0, 32);
418
+ const s = sigBuf.subarray(32, 64);
419
+ let v = sigBuf[64];
420
+ // Normalize v: Ethereum uses 27/28, Stacks uses 0/1
421
+ if (v >= 27)
422
+ v -= 27;
423
+ return { recoveryId: v, r, s };
424
+ }
425
+ else if (sigBuf.length === 64) {
426
+ // Compact format: r(32) + s(32), assume recovery ID 0
427
+ return {
428
+ recoveryId: 0,
429
+ r: sigBuf.subarray(0, 32),
430
+ s: sigBuf.subarray(32, 64),
431
+ };
432
+ }
433
+ throw new Error(`Unexpected signature length: ${sigBuf.length} bytes`);
434
+ }
435
+ // ────────────────────────────────────────────────
436
+ // Private: RPC helpers
437
+ // ────────────────────────────────────────────────
438
+ async fetchNonce(address) {
439
+ const response = await fetch(`${this.rpcUrl}/v2/accounts/${address}?proof=0`);
440
+ if (!response.ok) {
441
+ throw new Error(`Failed to fetch nonce: ${response.status}`);
442
+ }
443
+ const data = await response.json();
444
+ return data.nonce;
445
+ }
446
+ async estimateFee() {
447
+ try {
448
+ const response = await fetch(`${this.rpcUrl}/v2/fees/transfer`);
449
+ if (!response.ok) {
450
+ // Default fee: 200 microSTX
451
+ return BigInt(200);
452
+ }
453
+ const fee = await response.json();
454
+ // The endpoint returns a number (estimated fee in microSTX)
455
+ return BigInt(fee);
456
+ }
457
+ catch {
458
+ return BigInt(200);
459
+ }
460
+ }
461
+ }
462
+ exports.StacksAdapter = StacksAdapter;
463
+ // ────────────────────────────────────────────────────
464
+ // Standalone utility functions
465
+ // ────────────────────────────────────────────────────
466
+ /**
467
+ * Hash160 = RIPEMD-160(SHA-256(data))
468
+ */
469
+ function hash160(data) {
470
+ const sha256 = crypto.createHash('sha256').update(data).digest();
471
+ return crypto.createHash('ripemd160').update(sha256).digest();
472
+ }
473
+ /**
474
+ * Write a 32-bit unsigned integer as big-endian.
475
+ */
476
+ function writeUint32BE(value) {
477
+ const buf = Buffer.alloc(4);
478
+ buf.writeUInt32BE(value, 0);
479
+ return buf;
480
+ }
481
+ /**
482
+ * Write a 64-bit unsigned integer as big-endian.
483
+ */
484
+ function writeUint64BE(value) {
485
+ const buf = Buffer.alloc(8);
486
+ buf.writeBigUInt64BE(value, 0);
487
+ return buf;
488
+ }
489
+ // ────────────────────────────────────────────────────
490
+ // c32check address decoding (minimal, no external deps)
491
+ // ────────────────────────────────────────────────────
492
+ const C32_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
493
+ /**
494
+ * Decode a Stacks c32check address (SP... or ST...) into version + hash160.
495
+ *
496
+ * c32check format: [version_char] [c32-encoded hash160] [4-byte checksum]
497
+ */
498
+ function decodec32Address(address) {
499
+ // Strip the leading 'S' prefix
500
+ if (!address.startsWith('S')) {
501
+ throw new Error(`Invalid Stacks address: must start with S, got: ${address}`);
502
+ }
503
+ // The version character is the second character
504
+ const versionChar = address[1];
505
+ let version;
506
+ switch (versionChar) {
507
+ case 'P':
508
+ version = 22;
509
+ break; // mainnet single-sig (P2PKH)
510
+ case 'M':
511
+ version = 20;
512
+ break; // mainnet multi-sig
513
+ case 'T':
514
+ version = 26;
515
+ break; // testnet single-sig
516
+ case 'N':
517
+ version = 21;
518
+ break; // testnet multi-sig
519
+ default:
520
+ throw new Error(`Unknown Stacks address version: S${versionChar}`);
521
+ }
522
+ // The rest is c32-encoded: hash160 (20 bytes) + checksum (4 bytes)
523
+ const dataStr = address.slice(2).toUpperCase();
524
+ const decoded = c32decode(dataStr);
525
+ // Last 4 bytes are checksum, first 20 are the hash160
526
+ if (decoded.length < 20) {
527
+ throw new Error(`Invalid Stacks address: decoded data too short`);
528
+ }
529
+ const hash = decoded.subarray(0, 20);
530
+ return { version, hash160: hash };
531
+ }
532
+ /**
533
+ * Decode a c32-encoded string into bytes.
534
+ *
535
+ * c32 is a base-32 encoding using the alphabet: 0123456789ABCDEFGHJKMNPQRSTVWXYZ
536
+ * (no I, L, O, U to avoid confusion).
537
+ */
538
+ function c32decode(input) {
539
+ if (input.length === 0)
540
+ return Buffer.alloc(0);
541
+ // Convert c32 characters to 5-bit values
542
+ const values = [];
543
+ for (const ch of input) {
544
+ const idx = C32_ALPHABET.indexOf(ch);
545
+ if (idx === -1) {
546
+ throw new Error(`Invalid c32 character: ${ch}`);
547
+ }
548
+ values.push(idx);
549
+ }
550
+ // Convert from base-32 to bytes using BigInt arithmetic
551
+ let num = BigInt(0);
552
+ for (const v of values) {
553
+ num = num * BigInt(32) + BigInt(v);
554
+ }
555
+ // Convert BigInt to byte array
556
+ const hexStr = num.toString(16);
557
+ const padded = hexStr.length % 2 === 0 ? hexStr : '0' + hexStr;
558
+ const bytes = Buffer.from(padded, 'hex');
559
+ // The result should be 24 bytes (20 hash + 4 checksum) for addresses
560
+ // Pad with leading zeros if needed
561
+ const expectedLen = 24;
562
+ if (bytes.length < expectedLen) {
563
+ return Buffer.concat([Buffer.alloc(expectedLen - bytes.length), bytes]);
564
+ }
565
+ return bytes;
566
+ }
567
+ // ────────────────────────────────────────────────────
568
+ // Factory functions
569
+ // ────────────────────────────────────────────────────
570
+ function createStacksAdapter(rpcUrl) {
571
+ return new StacksAdapter('mainnet', rpcUrl);
572
+ }
573
+ function createStacksTestnetAdapter(rpcUrl) {
574
+ return new StacksAdapter('testnet', rpcUrl);
575
+ }
576
+ //# sourceMappingURL=stacks.js.map