@secondlayer/stacks 2.6.0 → 2.7.0
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 +65 -0
- package/dist/shared/{stacks-yb8xesdm.js → stacks-a48qj7ss.js} +3 -3
- package/dist/shared/{stacks-hr5rg2et.js → stacks-k96f5g1a.js} +2 -2
- package/dist/shared/{stacks-gxhznn3h.js → stacks-r3mqgw2d.js} +47 -3
- package/dist/shared/stacks-r3mqgw2d.js.map +15 -0
- package/dist/shared/{stacks-ffsv2t3t.js → stacks-s6v8k4j5.js} +2 -2
- package/dist/shared/{stacks-a9cn5ehj.js → stacks-txh6dadn.js} +2 -2
- package/dist/shared/{stacks-1wra341e.cjs → stacks-wb06pevp.cjs} +46 -2
- package/dist/shared/stacks-wb06pevp.cjs.map +15 -0
- package/dist/src/actions/index.d.cts +5 -0
- package/dist/src/actions/index.d.ts +5 -0
- package/dist/src/actions/index.js +5 -5
- package/dist/src/bns/index.d.cts +5 -0
- package/dist/src/bns/index.d.ts +5 -0
- package/dist/src/bns/index.js +3 -3
- package/dist/src/index.cjs +102 -1
- package/dist/src/index.cjs.map +5 -4
- package/dist/src/index.d.cts +150 -1
- package/dist/src/index.d.ts +150 -1
- package/dist/src/index.js +109 -6
- package/dist/src/index.js.map +5 -4
- package/dist/src/pox/index.d.cts +5 -0
- package/dist/src/pox/index.d.ts +5 -0
- package/dist/src/pox/index.js +3 -3
- package/dist/src/sbtc/index.d.cts +5 -0
- package/dist/src/sbtc/index.d.ts +5 -0
- package/dist/src/sbtc/index.js +3 -3
- package/dist/src/stackingdao/index.d.cts +5 -0
- package/dist/src/stackingdao/index.d.ts +5 -0
- package/dist/src/stackingdao/index.js +3 -3
- package/dist/src/subscriptions/index.d.cts +5 -0
- package/dist/src/subscriptions/index.d.ts +5 -0
- package/dist/src/tools/index.cjs +1 -1
- package/dist/src/tools/index.cjs.map +3 -3
- package/dist/src/tools/index.d.cts +5 -0
- package/dist/src/tools/index.d.ts +5 -0
- package/dist/src/tools/index.js +1 -1
- package/dist/src/tools/index.js.map +3 -3
- package/dist/src/x402/index.d.cts +5 -0
- package/dist/src/x402/index.d.ts +5 -0
- package/dist/src/x402/index.js +2 -2
- package/package.json +1 -1
- package/dist/shared/stacks-1wra341e.cjs.map +0 -15
- package/dist/shared/stacks-gxhznn3h.js.map +0 -15
- /package/dist/shared/{stacks-yb8xesdm.js.map → stacks-a48qj7ss.js.map} +0 -0
- /package/dist/shared/{stacks-hr5rg2et.js.map → stacks-k96f5g1a.js.map} +0 -0
- /package/dist/shared/{stacks-ffsv2t3t.js.map → stacks-s6v8k4j5.js.map} +0 -0
- /package/dist/shared/{stacks-a9cn5ehj.js.map → stacks-txh6dadn.js.map} +0 -0
package/README.md
CHANGED
|
@@ -52,6 +52,71 @@ work; Secondlayer is a data-infrastructure company, not a wallet SDK vendor.
|
|
|
52
52
|
| `@secondlayer/stacks/pox` | PoX stacking — solo and delegated |
|
|
53
53
|
| `@secondlayer/stacks/stackingdao` | StackingDAO liquid staking (STX/stSTX) |
|
|
54
54
|
|
|
55
|
+
## Nonce management
|
|
56
|
+
|
|
57
|
+
Stacks' `/v2/accounts` returns only the confirmed nonce — it ignores the mempool. Broadcasting several transactions from one account before the first confirms makes them reuse the same nonce, so every one after the first is rejected (`ConflictingNonceInMempool`). The usual workaround is tracking nonces by hand.
|
|
58
|
+
|
|
59
|
+
Attach a nonce manager and the SDK hands out sequential nonces across rapid broadcasts:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createWalletClient, http, createNonceManager } from "@secondlayer/stacks";
|
|
63
|
+
import { mainnet } from "@secondlayer/stacks/chains";
|
|
64
|
+
import { privateKeyToAccount } from "@secondlayer/stacks/accounts";
|
|
65
|
+
|
|
66
|
+
const client = createWalletClient({
|
|
67
|
+
chain: mainnet,
|
|
68
|
+
transport: http(), // any node — no Secondlayer dependency
|
|
69
|
+
account: privateKeyToAccount(process.env.KEY!),
|
|
70
|
+
nonceManager: createNonceManager(), // jsonRpcSource + in-memory store
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 20 back-to-back transfers → nonces n, n+1, …, n+19 — no collisions
|
|
74
|
+
await Promise.all(
|
|
75
|
+
recipients.map((to) => client.transferStx({ to, amount: 1000n })),
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Passing an explicit `nonce` always bypasses the manager. The defaults are node-agnostic and in-memory — zero external dependencies.
|
|
80
|
+
|
|
81
|
+
### Multiple processes / smart wallets
|
|
82
|
+
|
|
83
|
+
The in-memory store is single-process. Backends that sign from one key across multiple workers (smart-wallet-as-a-service) need a shared, durable store. The reservation is atomic in the datastore, so it doubles as the cross-process lock and survives restarts:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { createNonceManager, redisStore } from "@secondlayer/stacks";
|
|
87
|
+
|
|
88
|
+
const nonceManager = createNonceManager({
|
|
89
|
+
store: redisStore({ redis: new Bun.RedisClient(process.env.REDIS_URL!) }),
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`postgresStore({ sql })` works the same way. Bring your own client — no global `Bun` reference, so the store stays runtime-agnostic.
|
|
94
|
+
|
|
95
|
+
### Mempool-aware sources (optional)
|
|
96
|
+
|
|
97
|
+
By default the floor is the node's confirmed nonce. To make it mempool-aware — and to auto-fill the freed nonce of a dropped transaction — swap the source. `indexSource` reads Secondlayer's mempool, `hiroNonceSource` reads Hiro's, or bring your own pending feed with `mempoolAwareSource`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import {
|
|
101
|
+
createNonceManager,
|
|
102
|
+
indexSource,
|
|
103
|
+
startNonceReconciler,
|
|
104
|
+
} from "@secondlayer/stacks";
|
|
105
|
+
|
|
106
|
+
// indexSource() | hiroNonceSource({ baseUrl }) | mempoolAwareSource({ getPending })
|
|
107
|
+
const source = indexSource();
|
|
108
|
+
const nonceManager = createNonceManager({ source });
|
|
109
|
+
|
|
110
|
+
// Optional: periodically heal silently-dropped txs (run in ONE process)
|
|
111
|
+
const reconciler = startNonceReconciler(nonceManager, {
|
|
112
|
+
client,
|
|
113
|
+
addresses: [account.address],
|
|
114
|
+
source,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Everything here is opt-in. With no `source`/`store`, the manager depends only on your node.
|
|
119
|
+
|
|
55
120
|
## WalletConnect v2
|
|
56
121
|
|
|
57
122
|
Native WC v2 implementation — X25519 ECDH, AES-256-GCM envelope encryption, Ed25519 JWT relay auth. Zero cost if you don't import it, tree-shakes completely.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readContract
|
|
3
|
-
} from "./stacks-
|
|
3
|
+
} from "./stacks-txh6dadn.js";
|
|
4
4
|
import {
|
|
5
5
|
broadcastWithNonceReset,
|
|
6
6
|
estimateFee,
|
|
7
7
|
isProviderAccount,
|
|
8
8
|
resolveNonce
|
|
9
|
-
} from "./stacks-
|
|
9
|
+
} from "./stacks-r3mqgw2d.js";
|
|
10
10
|
import {
|
|
11
11
|
BaseError
|
|
12
12
|
} from "./stacks-709tra19.js";
|
|
@@ -309,4 +309,4 @@ async function transferStx(client, params) {
|
|
|
309
309
|
export { getAccountInfo, getBalance, getBlock, getBlockHeight, getContractAbi, multicall, SimulationError, simulateCall, simulateTransaction, deployContract, signMessage, signTransactionAction, transferStx };
|
|
310
310
|
|
|
311
311
|
//# debugId=B8138CC37EDC138164756E2164756E21
|
|
312
|
-
//# sourceMappingURL=stacks-
|
|
312
|
+
//# sourceMappingURL=stacks-a48qj7ss.js.map
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
callContract,
|
|
3
3
|
getMapEntry,
|
|
4
4
|
readContract
|
|
5
|
-
} from "./stacks-
|
|
5
|
+
} from "./stacks-txh6dadn.js";
|
|
6
6
|
import {
|
|
7
7
|
clarityValueToJSUntyped,
|
|
8
8
|
jsToClarityValue,
|
|
@@ -131,4 +131,4 @@ class ContractResponseError extends Error {
|
|
|
131
131
|
export { getContract, ContractResponseError };
|
|
132
132
|
|
|
133
133
|
//# debugId=C71C39F2B8094B6864756E2164756E21
|
|
134
|
-
//# sourceMappingURL=stacks-
|
|
134
|
+
//# sourceMappingURL=stacks-k96f5g1a.js.map
|
|
@@ -106,6 +106,9 @@ function memoryStore() {
|
|
|
106
106
|
},
|
|
107
107
|
reset(key) {
|
|
108
108
|
next.delete(key);
|
|
109
|
+
},
|
|
110
|
+
peek(key) {
|
|
111
|
+
return next.get(key);
|
|
109
112
|
}
|
|
110
113
|
};
|
|
111
114
|
}
|
|
@@ -121,6 +124,9 @@ function createNonceManager(params = {}) {
|
|
|
121
124
|
},
|
|
122
125
|
reset({ client, address }) {
|
|
123
126
|
return store.reset(nonceKey(client, address));
|
|
127
|
+
},
|
|
128
|
+
async peek({ client, address }) {
|
|
129
|
+
return store.peek?.(nonceKey(client, address));
|
|
124
130
|
}
|
|
125
131
|
};
|
|
126
132
|
}
|
|
@@ -129,6 +135,44 @@ async function resolveNonce(client, address) {
|
|
|
129
135
|
return client.nonceManager.consume({ client, address });
|
|
130
136
|
return getNonce(client, { address });
|
|
131
137
|
}
|
|
138
|
+
async function reconcileNonce(manager, params) {
|
|
139
|
+
const { client, address, source } = params;
|
|
140
|
+
const downward = params.downward ?? true;
|
|
141
|
+
const authoritative = await source.get({ client, address });
|
|
142
|
+
const tracked = await manager.peek({ client, address });
|
|
143
|
+
if (tracked === undefined || authoritative === tracked) {
|
|
144
|
+
return { reset: false, authoritative, tracked };
|
|
145
|
+
}
|
|
146
|
+
if (authoritative < tracked && !downward) {
|
|
147
|
+
return { reset: false, authoritative, tracked };
|
|
148
|
+
}
|
|
149
|
+
await manager.reset({ client, address });
|
|
150
|
+
return { reset: true, authoritative, tracked };
|
|
151
|
+
}
|
|
152
|
+
function startNonceReconciler(manager, params) {
|
|
153
|
+
const { client, addresses, source } = params;
|
|
154
|
+
const intervalMs = params.intervalMs ?? 60000;
|
|
155
|
+
const tick = async () => {
|
|
156
|
+
for (const address of addresses) {
|
|
157
|
+
try {
|
|
158
|
+
const result = await reconcileNonce(manager, {
|
|
159
|
+
client,
|
|
160
|
+
address,
|
|
161
|
+
source,
|
|
162
|
+
downward: params.downward
|
|
163
|
+
});
|
|
164
|
+
params.onReconcile?.(address, result);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
params.onError?.(address, error);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const timer = setInterval(tick, intervalMs);
|
|
171
|
+
timer.unref?.();
|
|
172
|
+
return {
|
|
173
|
+
stop: () => clearInterval(timer)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
132
176
|
function isNonceConflictError(error) {
|
|
133
177
|
if (!(error instanceof BroadcastError))
|
|
134
178
|
return false;
|
|
@@ -154,7 +198,7 @@ function isProviderAccount(account) {
|
|
|
154
198
|
return account.type === "provider";
|
|
155
199
|
}
|
|
156
200
|
|
|
157
|
-
export { estimateFee, getNonce, TransactionError, BroadcastError, SerializationError, SigningError, sendTransaction, jsonRpcSource, memoryStore, createNonceManager, resolveNonce, isNonceConflictError, broadcastWithNonceReset, isProviderAccount };
|
|
201
|
+
export { estimateFee, getNonce, TransactionError, BroadcastError, SerializationError, SigningError, sendTransaction, jsonRpcSource, memoryStore, createNonceManager, resolveNonce, reconcileNonce, startNonceReconciler, isNonceConflictError, broadcastWithNonceReset, isProviderAccount };
|
|
158
202
|
|
|
159
|
-
//# debugId=
|
|
160
|
-
//# sourceMappingURL=stacks-
|
|
203
|
+
//# debugId=201E1A3233967AC964756E2164756E21
|
|
204
|
+
//# sourceMappingURL=stacks-r3mqgw2d.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/actions/public/estimateFee.ts", "../src/actions/public/getNonce.ts", "../src/errors/transaction.ts", "../src/actions/wallet/sendTransaction.ts", "../src/actions/wallet/nonceManager.ts", "../src/actions/wallet/utils.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { Client } from \"../../clients/types.ts\";\nimport type { StacksTransaction } from \"../../transactions/types.ts\";\nimport {\n\tserializePayload,\n\tserializeTransaction,\n} from \"../../transactions/wire/serialize.ts\";\nimport { bytesToHex } from \"../../utils/encoding.ts\";\n\nexport type EstimateFeeParams = {\n\ttransaction: StacksTransaction;\n};\n\nexport type FeeEstimation = {\n\tfeeRate: number;\n\tfee: number;\n};\n\nexport async function estimateFee(\n\tclient: Client,\n\tparams: EstimateFeeParams,\n): Promise<FeeEstimation[]> {\n\tconst payloadHex = bytesToHex(serializePayload(params.transaction.payload));\n\tconst txHex = bytesToHex(serializeTransaction(params.transaction));\n\n\tconst data = await client.request(\"/v2/fees/transaction\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\testimated_len: Math.ceil(txHex.length / 2),\n\t\t\ttransaction_payload: `0x${payloadHex}`,\n\t\t},\n\t});\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\treturn (data.estimations ?? []).map((e: any) => ({\n\t\tfeeRate: e.fee_rate,\n\t\tfee: e.fee,\n\t}));\n}\n",
|
|
6
|
+
"import type { Client } from \"../../clients/types.ts\";\n\nexport type GetNonceParams = {\n\taddress: string;\n};\n\nexport type GetNonceResult = {\n\tnonce: bigint;\n\tpossibleNextNonce: bigint;\n};\n\nexport async function getNonce(\n\tclient: Client,\n\tparams: GetNonceParams,\n): Promise<bigint> {\n\tconst data = await client.request(`/v2/accounts/${params.address}`, {\n\t\tmethod: \"GET\",\n\t});\n\treturn BigInt(data.nonce);\n}\n",
|
|
7
|
+
"import { BaseError } from \"./base.ts\";\n\nexport class TransactionError extends BaseError {\n\toverride name = \"TransactionError\";\n}\n\nexport class BroadcastError extends BaseError {\n\toverride name = \"BroadcastError\";\n\ttxid?: string;\n\treason?: string;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions?: { cause?: Error; txid?: string; reason?: string },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.txid = options?.txid;\n\t\tthis.reason = options?.reason;\n\t}\n}\n\nexport class SerializationError extends BaseError {\n\toverride name = \"SerializationError\";\n}\n\nexport class SigningError extends BaseError {\n\toverride name = \"SigningError\";\n}\n",
|
|
8
|
+
"import type { Client } from \"../../clients/types.ts\";\nimport { BroadcastError } from \"../../errors/transaction.ts\";\nimport { getTransactionId } from \"../../transactions/signer.ts\";\nimport type { StacksTransaction } from \"../../transactions/types.ts\";\nimport { serializeTransaction } from \"../../transactions/wire/serialize.ts\";\nimport { bytesToHex } from \"../../utils/encoding.ts\";\n\nexport type SendTransactionParams = {\n\ttransaction: StacksTransaction;\n\tattachment?: Uint8Array | string;\n};\n\nexport type SendTransactionResult = {\n\ttxid: string;\n};\n\n/** Broadcast a signed transaction to the network */\nexport async function sendTransaction(\n\tclient: Client,\n\tparams: SendTransactionParams,\n): Promise<SendTransactionResult> {\n\tconst hex = bytesToHex(serializeTransaction(params.transaction));\n\n\tconst body: Record<string, string> = { tx: hex };\n\tif (params.attachment) {\n\t\tbody.attachment =\n\t\t\ttypeof params.attachment === \"string\"\n\t\t\t\t? params.attachment\n\t\t\t\t: bytesToHex(params.attachment);\n\t}\n\n\tconst data = await client.request(\"/v2/transactions\", {\n\t\tmethod: \"POST\",\n\t\tbody,\n\t});\n\n\tif (data.error) {\n\t\tthrow new BroadcastError(data.reason ?? data.error, {\n\t\t\treason: data.reason,\n\t\t});\n\t}\n\n\tconst txid =\n\t\ttypeof data === \"string\"\n\t\t\t? data.replace(/\"/g, \"\")\n\t\t\t: (data.txid ?? getTransactionId(params.transaction));\n\n\treturn { txid };\n}\n",
|
|
9
|
+
"import type { Client } from \"../../clients/types.ts\";\nimport { BroadcastError } from \"../../errors/transaction.ts\";\nimport type { StacksTransaction } from \"../../transactions/types.ts\";\nimport { getNonce } from \"../public/getNonce.ts\";\nimport { sendTransaction } from \"./sendTransaction.ts\";\n\n/**\n * Provides the confirmed on-chain nonce floor for an address.\n *\n * The default {@link jsonRpcSource} reads the configured node's `/v2/accounts`\n * endpoint — node-agnostic, no Hiro dependency. Other sources (mempool-aware,\n * first-party Index) can be swapped in without touching the manager.\n */\nexport type NonceManagerSource = {\n\tget(params: { client: Client; address: string }): Promise<bigint>;\n};\n\n/**\n * Holds per-address allocation state and hands out the next nonce.\n *\n * `reserve` MUST be atomic per `key`: two concurrent reservations for the same\n * key must never return the same value. The in-memory {@link memoryStore}\n * serializes with a per-key promise chain; a persisted store (Redis `INCR`,\n * Postgres `SELECT ... FOR UPDATE`) becomes the cross-process lock for the\n * multi-builder / smart-wallet-as-a-service case.\n */\nexport type NonceStore = {\n\t/**\n\t * Reserve the next nonce for `key`. `getFloor` reads the confirmed on-chain\n\t * nonce; it is only invoked when the store has no tracked value (cold start\n\t * or after {@link NonceStore.reset}).\n\t */\n\treserve(key: string, getFloor: () => Promise<bigint>): Promise<bigint>;\n\t/** Forget tracked state for `key` so the next reserve re-syncs from the floor. */\n\treset(key: string): void | Promise<void>;\n\t/**\n\t * Return the next nonce that {@link NonceStore.reserve} would hand out for\n\t * `key` WITHOUT consuming it, or `undefined` if `key` is untracked. Used by\n\t * {@link reconcileNonce} to detect drift. Optional — stores that omit it\n\t * simply opt out of reconciliation.\n\t */\n\tpeek?(key: string): Promise<bigint | undefined> | bigint | undefined;\n};\n\n/** Allocates mempool-safe sequential nonces across rapid broadcasts from one account. */\nexport type NonceManager = {\n\tconsume(params: { client: Client; address: string }): Promise<bigint>;\n\treset(params: { client: Client; address: string }): void | Promise<void>;\n\t/** Next nonce that {@link NonceManager.consume} would return without consuming it, or `undefined` if untracked. */\n\tpeek(params: {\n\t\tclient: Client;\n\t\taddress: string;\n\t}): Promise<bigint | undefined>;\n};\n\nexport type CreateNonceManagerParams = {\n\tsource?: NonceManagerSource;\n\tstore?: NonceStore;\n};\n\n/** Confirmed-nonce source backed by the configured node's `/v2/accounts` RPC (no Hiro dependency). */\nexport function jsonRpcSource(): NonceManagerSource {\n\treturn {\n\t\tget: ({ client, address }) => getNonce(client, { address }),\n\t};\n}\n\n/**\n * In-memory, single-process store. Tracks the next nonce per key and serializes\n * concurrent reservations with a per-key promise chain.\n *\n * Correct only within one process — swap in a persisted store for multi-process\n * deployments that share a signing key.\n */\nexport function memoryStore(): NonceStore {\n\tconst next = new Map<string, bigint>();\n\tconst locks = new Map<string, Promise<unknown>>();\n\n\tfunction withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n\t\tconst prev = locks.get(key) ?? Promise.resolve();\n\t\t// Chain onto the prior reservation regardless of how it settled, so a\n\t\t// rejected reserve never wedges the key.\n\t\tconst run = prev.then(fn, fn);\n\t\tlocks.set(\n\t\t\tkey,\n\t\t\trun.catch(() => {}),\n\t\t);\n\t\treturn run;\n\t}\n\n\treturn {\n\t\treserve(key, getFloor) {\n\t\t\treturn withLock(key, async () => {\n\t\t\t\tlet current = next.get(key);\n\t\t\t\tif (current === undefined) current = await getFloor();\n\t\t\t\tnext.set(key, current + 1n);\n\t\t\t\treturn current;\n\t\t\t});\n\t\t},\n\t\treset(key) {\n\t\t\tnext.delete(key);\n\t\t},\n\t\tpeek(key) {\n\t\t\treturn next.get(key);\n\t\t},\n\t};\n}\n\nfunction nonceKey(client: Client, address: string): string {\n\treturn `${client.chain?.id ?? \"stacks\"}:${address}`;\n}\n\n/**\n * Create a nonce manager that floors on a confirmed-nonce {@link NonceManagerSource}\n * and increments a {@link NonceStore} on every {@link NonceManager.consume}.\n *\n * Defaults to {@link jsonRpcSource} + {@link memoryStore} — node-agnostic,\n * single-process, zero external dependencies.\n */\nexport function createNonceManager(\n\tparams: CreateNonceManagerParams = {},\n): NonceManager {\n\tconst source = params.source ?? jsonRpcSource();\n\tconst store = params.store ?? memoryStore();\n\n\treturn {\n\t\tconsume({ client, address }) {\n\t\t\treturn store.reserve(nonceKey(client, address), () =>\n\t\t\t\tsource.get({ client, address }),\n\t\t\t);\n\t\t},\n\t\treset({ client, address }) {\n\t\t\treturn store.reset(nonceKey(client, address));\n\t\t},\n\t\tasync peek({ client, address }) {\n\t\t\treturn store.peek?.(nonceKey(client, address));\n\t\t},\n\t};\n}\n\n/** Resolve the next nonce via the client's nonce manager, falling back to a confirmed read. */\nexport async function resolveNonce(\n\tclient: Client,\n\taddress: string,\n): Promise<bigint> {\n\tif (client.nonceManager)\n\t\treturn client.nonceManager.consume({ client, address });\n\treturn getNonce(client, { address });\n}\n\nexport type ReconcileNonceParams = {\n\tclient: Client;\n\taddress: string;\n\t/** Authoritative (ideally mempool-aware) source to reconcile against. */\n\tsource: NonceManagerSource;\n\t/**\n\t * Allow resetting DOWNWARD when the source's next nonce is below what the\n\t * store tracks — i.e. a previously-allocated tx is no longer pending or\n\t * confirmed (dropped/GC'd), so its nonce should be reused. Default `true`.\n\t *\n\t * Downward resets rely on `source` being current: with a go-forward mempool\n\t * source that lags broadcast, run reconciliation on an interval comfortably\n\t * longer than mempool propagation so a still-missing tx is genuinely dropped.\n\t * Set `false` for upward-only reconciliation (always safe).\n\t */\n\tdownward?: boolean;\n};\n\nexport type ReconcileNonceResult = {\n\t/** Whether the manager was reset (drift detected). */\n\treset: boolean;\n\t/** Authoritative next nonce from `source`. */\n\tauthoritative: bigint;\n\t/** Next nonce the store tracked, or `undefined` if untracked. */\n\ttracked: bigint | undefined;\n};\n\n/**\n * Reconcile a tracked nonce against an authoritative source, healing silent\n * drift that produces no broadcast error (a dropped/GC'd mempool tx leaving the\n * counter overshot, or the chain advancing past the local view).\n *\n * When the source's next nonce differs from the tracked value, the manager is\n * {@link NonceManager.reset}; the next `consume` re-seeds from `source`. A no-op\n * if the store is untracked or does not implement `peek`.\n */\nexport async function reconcileNonce(\n\tmanager: NonceManager,\n\tparams: ReconcileNonceParams,\n): Promise<ReconcileNonceResult> {\n\tconst { client, address, source } = params;\n\tconst downward = params.downward ?? true;\n\n\tconst authoritative = await source.get({ client, address });\n\tconst tracked = await manager.peek({ client, address });\n\n\tif (tracked === undefined || authoritative === tracked) {\n\t\treturn { reset: false, authoritative, tracked };\n\t}\n\tif (authoritative < tracked && !downward) {\n\t\treturn { reset: false, authoritative, tracked };\n\t}\n\n\tawait manager.reset({ client, address });\n\treturn { reset: true, authoritative, tracked };\n}\n\nexport type StartNonceReconcilerParams = {\n\tclient: Client;\n\taddresses: string[];\n\tsource: NonceManagerSource;\n\t/** Reconcile interval in ms. Default 60_000. Keep well above mempool propagation. */\n\tintervalMs?: number;\n\tdownward?: boolean;\n\t/** Per-address callback after each reconcile (observability). */\n\tonReconcile?: (address: string, result: ReconcileNonceResult) => void;\n\t/** Per-address error callback; reconciliation continues on the next tick. */\n\tonError?: (address: string, error: unknown) => void;\n};\n\n/**\n * Run {@link reconcileNonce} on a timer for a set of addresses.\n *\n * SINGLE-WRITER: run this in exactly ONE process. With a shared persisted store,\n * a reconciler resetting the counter while other workers allocate is racy — keep\n * reconciliation on one designated process and let the others only allocate.\n *\n * Returns a handle; call `stop()` to clear the timer.\n */\nexport function startNonceReconciler(\n\tmanager: NonceManager,\n\tparams: StartNonceReconcilerParams,\n): { stop: () => void } {\n\tconst { client, addresses, source } = params;\n\tconst intervalMs = params.intervalMs ?? 60_000;\n\n\tconst tick = async () => {\n\t\tfor (const address of addresses) {\n\t\t\ttry {\n\t\t\t\tconst result = await reconcileNonce(manager, {\n\t\t\t\t\tclient,\n\t\t\t\t\taddress,\n\t\t\t\t\tsource,\n\t\t\t\t\tdownward: params.downward,\n\t\t\t\t});\n\t\t\t\tparams.onReconcile?.(address, result);\n\t\t\t} catch (error) {\n\t\t\t\tparams.onError?.(address, error);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst timer = setInterval(tick, intervalMs);\n\t// Don't keep the event loop alive solely for reconciliation (Node/Bun).\n\t(timer as { unref?: () => void }).unref?.();\n\n\treturn {\n\t\tstop: () => clearInterval(timer),\n\t};\n}\n\n/** True when a broadcast was rejected for a nonce conflict (`ConflictingNonceInMempool`, `BadNonce`). */\nexport function isNonceConflictError(error: unknown): boolean {\n\tif (!(error instanceof BroadcastError)) return false;\n\tconst haystack = `${error.reason ?? \"\"} ${error.message}`.toLowerCase();\n\treturn haystack.includes(\"nonce\");\n}\n\n/**\n * Broadcast a signed transaction; on a nonce-conflict rejection, reset the\n * manager so the next build re-syncs to the confirmed floor.\n */\nexport async function broadcastWithNonceReset(\n\tclient: Client,\n\tparams: { transaction: StacksTransaction; address: string },\n): Promise<string> {\n\ttry {\n\t\tconst result = await sendTransaction(client, {\n\t\t\ttransaction: params.transaction,\n\t\t});\n\t\treturn result.txid;\n\t} catch (error) {\n\t\tif (client.nonceManager && isNonceConflictError(error)) {\n\t\t\tawait client.nonceManager.reset({ client, address: params.address });\n\t\t}\n\t\tthrow error;\n\t}\n}\n",
|
|
10
|
+
"import type { ProviderAccount } from \"../../accounts/types.ts\";\nimport type { Account } from \"../../clients/types.ts\";\n\nexport function isProviderAccount(\n\taccount: Account,\n): account is ProviderAccount {\n\treturn account.type === \"provider\";\n}\n"
|
|
11
|
+
],
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "201E1A3233967AC964756E2164756E21",
|
|
14
|
+
"names": []
|
|
15
|
+
}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
estimateFee,
|
|
3
3
|
isProviderAccount,
|
|
4
4
|
resolveNonce
|
|
5
|
-
} from "./stacks-
|
|
5
|
+
} from "./stacks-r3mqgw2d.js";
|
|
6
6
|
import {
|
|
7
7
|
AuthType,
|
|
8
8
|
createSingleSigSpendingCondition,
|
|
@@ -48,4 +48,4 @@ async function sponsorTransaction(client, params) {
|
|
|
48
48
|
export { sponsorTransaction };
|
|
49
49
|
|
|
50
50
|
//# debugId=B5D2826EE8DC537B64756E2164756E21
|
|
51
|
-
//# sourceMappingURL=stacks-
|
|
51
|
+
//# sourceMappingURL=stacks-s6v8k4j5.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
estimateFee,
|
|
4
4
|
isProviderAccount,
|
|
5
5
|
resolveNonce
|
|
6
|
-
} from "./stacks-
|
|
6
|
+
} from "./stacks-r3mqgw2d.js";
|
|
7
7
|
import {
|
|
8
8
|
buildContractCall,
|
|
9
9
|
signTransactionWithAccount
|
|
@@ -93,4 +93,4 @@ async function callContract(client, params) {
|
|
|
93
93
|
export { getMapEntry, readContract, callContract };
|
|
94
94
|
|
|
95
95
|
//# debugId=A951FF5E2E3C07E364756E2164756E21
|
|
96
|
-
//# sourceMappingURL=stacks-
|
|
96
|
+
//# sourceMappingURL=stacks-txh6dadn.js.map
|
|
@@ -94,6 +94,9 @@ function memoryStore() {
|
|
|
94
94
|
},
|
|
95
95
|
reset(key) {
|
|
96
96
|
next.delete(key);
|
|
97
|
+
},
|
|
98
|
+
peek(key) {
|
|
99
|
+
return next.get(key);
|
|
97
100
|
}
|
|
98
101
|
};
|
|
99
102
|
}
|
|
@@ -109,6 +112,9 @@ function createNonceManager(params = {}) {
|
|
|
109
112
|
},
|
|
110
113
|
reset({ client, address }) {
|
|
111
114
|
return store.reset(nonceKey(client, address));
|
|
115
|
+
},
|
|
116
|
+
async peek({ client, address }) {
|
|
117
|
+
return store.peek?.(nonceKey(client, address));
|
|
112
118
|
}
|
|
113
119
|
};
|
|
114
120
|
}
|
|
@@ -117,6 +123,44 @@ async function resolveNonce(client, address) {
|
|
|
117
123
|
return client.nonceManager.consume({ client, address });
|
|
118
124
|
return getNonce(client, { address });
|
|
119
125
|
}
|
|
126
|
+
async function reconcileNonce(manager, params) {
|
|
127
|
+
const { client, address, source } = params;
|
|
128
|
+
const downward = params.downward ?? true;
|
|
129
|
+
const authoritative = await source.get({ client, address });
|
|
130
|
+
const tracked = await manager.peek({ client, address });
|
|
131
|
+
if (tracked === undefined || authoritative === tracked) {
|
|
132
|
+
return { reset: false, authoritative, tracked };
|
|
133
|
+
}
|
|
134
|
+
if (authoritative < tracked && !downward) {
|
|
135
|
+
return { reset: false, authoritative, tracked };
|
|
136
|
+
}
|
|
137
|
+
await manager.reset({ client, address });
|
|
138
|
+
return { reset: true, authoritative, tracked };
|
|
139
|
+
}
|
|
140
|
+
function startNonceReconciler(manager, params) {
|
|
141
|
+
const { client, addresses, source } = params;
|
|
142
|
+
const intervalMs = params.intervalMs ?? 60000;
|
|
143
|
+
const tick = async () => {
|
|
144
|
+
for (const address of addresses) {
|
|
145
|
+
try {
|
|
146
|
+
const result = await reconcileNonce(manager, {
|
|
147
|
+
client,
|
|
148
|
+
address,
|
|
149
|
+
source,
|
|
150
|
+
downward: params.downward
|
|
151
|
+
});
|
|
152
|
+
params.onReconcile?.(address, result);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
params.onError?.(address, error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const timer = setInterval(tick, intervalMs);
|
|
159
|
+
timer.unref?.();
|
|
160
|
+
return {
|
|
161
|
+
stop: () => clearInterval(timer)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
120
164
|
function isNonceConflictError(error) {
|
|
121
165
|
if (!(error instanceof BroadcastError))
|
|
122
166
|
return false;
|
|
@@ -142,5 +186,5 @@ function isProviderAccount(account) {
|
|
|
142
186
|
return account.type === "provider";
|
|
143
187
|
}
|
|
144
188
|
|
|
145
|
-
//# debugId=
|
|
146
|
-
//# sourceMappingURL=stacks-
|
|
189
|
+
//# debugId=DB5D2C7E88D037B264756E2164756E21
|
|
190
|
+
//# sourceMappingURL=stacks-wb06pevp.cjs.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/actions/public/estimateFee.ts", "../src/actions/public/getNonce.ts", "../src/errors/transaction.ts", "../src/actions/wallet/sendTransaction.ts", "../src/actions/wallet/nonceManager.ts", "../src/actions/wallet/utils.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { Client } from \"../../clients/types.ts\";\nimport type { StacksTransaction } from \"../../transactions/types.ts\";\nimport {\n\tserializePayload,\n\tserializeTransaction,\n} from \"../../transactions/wire/serialize.ts\";\nimport { bytesToHex } from \"../../utils/encoding.ts\";\n\nexport type EstimateFeeParams = {\n\ttransaction: StacksTransaction;\n};\n\nexport type FeeEstimation = {\n\tfeeRate: number;\n\tfee: number;\n};\n\nexport async function estimateFee(\n\tclient: Client,\n\tparams: EstimateFeeParams,\n): Promise<FeeEstimation[]> {\n\tconst payloadHex = bytesToHex(serializePayload(params.transaction.payload));\n\tconst txHex = bytesToHex(serializeTransaction(params.transaction));\n\n\tconst data = await client.request(\"/v2/fees/transaction\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\testimated_len: Math.ceil(txHex.length / 2),\n\t\t\ttransaction_payload: `0x${payloadHex}`,\n\t\t},\n\t});\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\treturn (data.estimations ?? []).map((e: any) => ({\n\t\tfeeRate: e.fee_rate,\n\t\tfee: e.fee,\n\t}));\n}\n",
|
|
6
|
+
"import type { Client } from \"../../clients/types.ts\";\n\nexport type GetNonceParams = {\n\taddress: string;\n};\n\nexport type GetNonceResult = {\n\tnonce: bigint;\n\tpossibleNextNonce: bigint;\n};\n\nexport async function getNonce(\n\tclient: Client,\n\tparams: GetNonceParams,\n): Promise<bigint> {\n\tconst data = await client.request(`/v2/accounts/${params.address}`, {\n\t\tmethod: \"GET\",\n\t});\n\treturn BigInt(data.nonce);\n}\n",
|
|
7
|
+
"import { BaseError } from \"./base.ts\";\n\nexport class TransactionError extends BaseError {\n\toverride name = \"TransactionError\";\n}\n\nexport class BroadcastError extends BaseError {\n\toverride name = \"BroadcastError\";\n\ttxid?: string;\n\treason?: string;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions?: { cause?: Error; txid?: string; reason?: string },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.txid = options?.txid;\n\t\tthis.reason = options?.reason;\n\t}\n}\n\nexport class SerializationError extends BaseError {\n\toverride name = \"SerializationError\";\n}\n\nexport class SigningError extends BaseError {\n\toverride name = \"SigningError\";\n}\n",
|
|
8
|
+
"import type { Client } from \"../../clients/types.ts\";\nimport { BroadcastError } from \"../../errors/transaction.ts\";\nimport { getTransactionId } from \"../../transactions/signer.ts\";\nimport type { StacksTransaction } from \"../../transactions/types.ts\";\nimport { serializeTransaction } from \"../../transactions/wire/serialize.ts\";\nimport { bytesToHex } from \"../../utils/encoding.ts\";\n\nexport type SendTransactionParams = {\n\ttransaction: StacksTransaction;\n\tattachment?: Uint8Array | string;\n};\n\nexport type SendTransactionResult = {\n\ttxid: string;\n};\n\n/** Broadcast a signed transaction to the network */\nexport async function sendTransaction(\n\tclient: Client,\n\tparams: SendTransactionParams,\n): Promise<SendTransactionResult> {\n\tconst hex = bytesToHex(serializeTransaction(params.transaction));\n\n\tconst body: Record<string, string> = { tx: hex };\n\tif (params.attachment) {\n\t\tbody.attachment =\n\t\t\ttypeof params.attachment === \"string\"\n\t\t\t\t? params.attachment\n\t\t\t\t: bytesToHex(params.attachment);\n\t}\n\n\tconst data = await client.request(\"/v2/transactions\", {\n\t\tmethod: \"POST\",\n\t\tbody,\n\t});\n\n\tif (data.error) {\n\t\tthrow new BroadcastError(data.reason ?? data.error, {\n\t\t\treason: data.reason,\n\t\t});\n\t}\n\n\tconst txid =\n\t\ttypeof data === \"string\"\n\t\t\t? data.replace(/\"/g, \"\")\n\t\t\t: (data.txid ?? getTransactionId(params.transaction));\n\n\treturn { txid };\n}\n",
|
|
9
|
+
"import type { Client } from \"../../clients/types.ts\";\nimport { BroadcastError } from \"../../errors/transaction.ts\";\nimport type { StacksTransaction } from \"../../transactions/types.ts\";\nimport { getNonce } from \"../public/getNonce.ts\";\nimport { sendTransaction } from \"./sendTransaction.ts\";\n\n/**\n * Provides the confirmed on-chain nonce floor for an address.\n *\n * The default {@link jsonRpcSource} reads the configured node's `/v2/accounts`\n * endpoint — node-agnostic, no Hiro dependency. Other sources (mempool-aware,\n * first-party Index) can be swapped in without touching the manager.\n */\nexport type NonceManagerSource = {\n\tget(params: { client: Client; address: string }): Promise<bigint>;\n};\n\n/**\n * Holds per-address allocation state and hands out the next nonce.\n *\n * `reserve` MUST be atomic per `key`: two concurrent reservations for the same\n * key must never return the same value. The in-memory {@link memoryStore}\n * serializes with a per-key promise chain; a persisted store (Redis `INCR`,\n * Postgres `SELECT ... FOR UPDATE`) becomes the cross-process lock for the\n * multi-builder / smart-wallet-as-a-service case.\n */\nexport type NonceStore = {\n\t/**\n\t * Reserve the next nonce for `key`. `getFloor` reads the confirmed on-chain\n\t * nonce; it is only invoked when the store has no tracked value (cold start\n\t * or after {@link NonceStore.reset}).\n\t */\n\treserve(key: string, getFloor: () => Promise<bigint>): Promise<bigint>;\n\t/** Forget tracked state for `key` so the next reserve re-syncs from the floor. */\n\treset(key: string): void | Promise<void>;\n\t/**\n\t * Return the next nonce that {@link NonceStore.reserve} would hand out for\n\t * `key` WITHOUT consuming it, or `undefined` if `key` is untracked. Used by\n\t * {@link reconcileNonce} to detect drift. Optional — stores that omit it\n\t * simply opt out of reconciliation.\n\t */\n\tpeek?(key: string): Promise<bigint | undefined> | bigint | undefined;\n};\n\n/** Allocates mempool-safe sequential nonces across rapid broadcasts from one account. */\nexport type NonceManager = {\n\tconsume(params: { client: Client; address: string }): Promise<bigint>;\n\treset(params: { client: Client; address: string }): void | Promise<void>;\n\t/** Next nonce that {@link NonceManager.consume} would return without consuming it, or `undefined` if untracked. */\n\tpeek(params: {\n\t\tclient: Client;\n\t\taddress: string;\n\t}): Promise<bigint | undefined>;\n};\n\nexport type CreateNonceManagerParams = {\n\tsource?: NonceManagerSource;\n\tstore?: NonceStore;\n};\n\n/** Confirmed-nonce source backed by the configured node's `/v2/accounts` RPC (no Hiro dependency). */\nexport function jsonRpcSource(): NonceManagerSource {\n\treturn {\n\t\tget: ({ client, address }) => getNonce(client, { address }),\n\t};\n}\n\n/**\n * In-memory, single-process store. Tracks the next nonce per key and serializes\n * concurrent reservations with a per-key promise chain.\n *\n * Correct only within one process — swap in a persisted store for multi-process\n * deployments that share a signing key.\n */\nexport function memoryStore(): NonceStore {\n\tconst next = new Map<string, bigint>();\n\tconst locks = new Map<string, Promise<unknown>>();\n\n\tfunction withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n\t\tconst prev = locks.get(key) ?? Promise.resolve();\n\t\t// Chain onto the prior reservation regardless of how it settled, so a\n\t\t// rejected reserve never wedges the key.\n\t\tconst run = prev.then(fn, fn);\n\t\tlocks.set(\n\t\t\tkey,\n\t\t\trun.catch(() => {}),\n\t\t);\n\t\treturn run;\n\t}\n\n\treturn {\n\t\treserve(key, getFloor) {\n\t\t\treturn withLock(key, async () => {\n\t\t\t\tlet current = next.get(key);\n\t\t\t\tif (current === undefined) current = await getFloor();\n\t\t\t\tnext.set(key, current + 1n);\n\t\t\t\treturn current;\n\t\t\t});\n\t\t},\n\t\treset(key) {\n\t\t\tnext.delete(key);\n\t\t},\n\t\tpeek(key) {\n\t\t\treturn next.get(key);\n\t\t},\n\t};\n}\n\nfunction nonceKey(client: Client, address: string): string {\n\treturn `${client.chain?.id ?? \"stacks\"}:${address}`;\n}\n\n/**\n * Create a nonce manager that floors on a confirmed-nonce {@link NonceManagerSource}\n * and increments a {@link NonceStore} on every {@link NonceManager.consume}.\n *\n * Defaults to {@link jsonRpcSource} + {@link memoryStore} — node-agnostic,\n * single-process, zero external dependencies.\n */\nexport function createNonceManager(\n\tparams: CreateNonceManagerParams = {},\n): NonceManager {\n\tconst source = params.source ?? jsonRpcSource();\n\tconst store = params.store ?? memoryStore();\n\n\treturn {\n\t\tconsume({ client, address }) {\n\t\t\treturn store.reserve(nonceKey(client, address), () =>\n\t\t\t\tsource.get({ client, address }),\n\t\t\t);\n\t\t},\n\t\treset({ client, address }) {\n\t\t\treturn store.reset(nonceKey(client, address));\n\t\t},\n\t\tasync peek({ client, address }) {\n\t\t\treturn store.peek?.(nonceKey(client, address));\n\t\t},\n\t};\n}\n\n/** Resolve the next nonce via the client's nonce manager, falling back to a confirmed read. */\nexport async function resolveNonce(\n\tclient: Client,\n\taddress: string,\n): Promise<bigint> {\n\tif (client.nonceManager)\n\t\treturn client.nonceManager.consume({ client, address });\n\treturn getNonce(client, { address });\n}\n\nexport type ReconcileNonceParams = {\n\tclient: Client;\n\taddress: string;\n\t/** Authoritative (ideally mempool-aware) source to reconcile against. */\n\tsource: NonceManagerSource;\n\t/**\n\t * Allow resetting DOWNWARD when the source's next nonce is below what the\n\t * store tracks — i.e. a previously-allocated tx is no longer pending or\n\t * confirmed (dropped/GC'd), so its nonce should be reused. Default `true`.\n\t *\n\t * Downward resets rely on `source` being current: with a go-forward mempool\n\t * source that lags broadcast, run reconciliation on an interval comfortably\n\t * longer than mempool propagation so a still-missing tx is genuinely dropped.\n\t * Set `false` for upward-only reconciliation (always safe).\n\t */\n\tdownward?: boolean;\n};\n\nexport type ReconcileNonceResult = {\n\t/** Whether the manager was reset (drift detected). */\n\treset: boolean;\n\t/** Authoritative next nonce from `source`. */\n\tauthoritative: bigint;\n\t/** Next nonce the store tracked, or `undefined` if untracked. */\n\ttracked: bigint | undefined;\n};\n\n/**\n * Reconcile a tracked nonce against an authoritative source, healing silent\n * drift that produces no broadcast error (a dropped/GC'd mempool tx leaving the\n * counter overshot, or the chain advancing past the local view).\n *\n * When the source's next nonce differs from the tracked value, the manager is\n * {@link NonceManager.reset}; the next `consume` re-seeds from `source`. A no-op\n * if the store is untracked or does not implement `peek`.\n */\nexport async function reconcileNonce(\n\tmanager: NonceManager,\n\tparams: ReconcileNonceParams,\n): Promise<ReconcileNonceResult> {\n\tconst { client, address, source } = params;\n\tconst downward = params.downward ?? true;\n\n\tconst authoritative = await source.get({ client, address });\n\tconst tracked = await manager.peek({ client, address });\n\n\tif (tracked === undefined || authoritative === tracked) {\n\t\treturn { reset: false, authoritative, tracked };\n\t}\n\tif (authoritative < tracked && !downward) {\n\t\treturn { reset: false, authoritative, tracked };\n\t}\n\n\tawait manager.reset({ client, address });\n\treturn { reset: true, authoritative, tracked };\n}\n\nexport type StartNonceReconcilerParams = {\n\tclient: Client;\n\taddresses: string[];\n\tsource: NonceManagerSource;\n\t/** Reconcile interval in ms. Default 60_000. Keep well above mempool propagation. */\n\tintervalMs?: number;\n\tdownward?: boolean;\n\t/** Per-address callback after each reconcile (observability). */\n\tonReconcile?: (address: string, result: ReconcileNonceResult) => void;\n\t/** Per-address error callback; reconciliation continues on the next tick. */\n\tonError?: (address: string, error: unknown) => void;\n};\n\n/**\n * Run {@link reconcileNonce} on a timer for a set of addresses.\n *\n * SINGLE-WRITER: run this in exactly ONE process. With a shared persisted store,\n * a reconciler resetting the counter while other workers allocate is racy — keep\n * reconciliation on one designated process and let the others only allocate.\n *\n * Returns a handle; call `stop()` to clear the timer.\n */\nexport function startNonceReconciler(\n\tmanager: NonceManager,\n\tparams: StartNonceReconcilerParams,\n): { stop: () => void } {\n\tconst { client, addresses, source } = params;\n\tconst intervalMs = params.intervalMs ?? 60_000;\n\n\tconst tick = async () => {\n\t\tfor (const address of addresses) {\n\t\t\ttry {\n\t\t\t\tconst result = await reconcileNonce(manager, {\n\t\t\t\t\tclient,\n\t\t\t\t\taddress,\n\t\t\t\t\tsource,\n\t\t\t\t\tdownward: params.downward,\n\t\t\t\t});\n\t\t\t\tparams.onReconcile?.(address, result);\n\t\t\t} catch (error) {\n\t\t\t\tparams.onError?.(address, error);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst timer = setInterval(tick, intervalMs);\n\t// Don't keep the event loop alive solely for reconciliation (Node/Bun).\n\t(timer as { unref?: () => void }).unref?.();\n\n\treturn {\n\t\tstop: () => clearInterval(timer),\n\t};\n}\n\n/** True when a broadcast was rejected for a nonce conflict (`ConflictingNonceInMempool`, `BadNonce`). */\nexport function isNonceConflictError(error: unknown): boolean {\n\tif (!(error instanceof BroadcastError)) return false;\n\tconst haystack = `${error.reason ?? \"\"} ${error.message}`.toLowerCase();\n\treturn haystack.includes(\"nonce\");\n}\n\n/**\n * Broadcast a signed transaction; on a nonce-conflict rejection, reset the\n * manager so the next build re-syncs to the confirmed floor.\n */\nexport async function broadcastWithNonceReset(\n\tclient: Client,\n\tparams: { transaction: StacksTransaction; address: string },\n): Promise<string> {\n\ttry {\n\t\tconst result = await sendTransaction(client, {\n\t\t\ttransaction: params.transaction,\n\t\t});\n\t\treturn result.txid;\n\t} catch (error) {\n\t\tif (client.nonceManager && isNonceConflictError(error)) {\n\t\t\tawait client.nonceManager.reset({ client, address: params.address });\n\t\t}\n\t\tthrow error;\n\t}\n}\n",
|
|
10
|
+
"import type { ProviderAccount } from \"../../accounts/types.ts\";\nimport type { Account } from \"../../clients/types.ts\";\n\nexport function isProviderAccount(\n\taccount: Account,\n): account is ProviderAccount {\n\treturn account.type === \"provider\";\n}\n"
|
|
11
|
+
],
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "DB5D2C7E88D037B264756E2164756E21",
|
|
14
|
+
"names": []
|
|
15
|
+
}
|
|
@@ -287,6 +287,11 @@ type NonceManager = {
|
|
|
287
287
|
client: Client
|
|
288
288
|
address: string
|
|
289
289
|
}): void | Promise<void>
|
|
290
|
+
/** Next nonce that {@link NonceManager.consume} would return without consuming it, or `undefined` if untracked. */
|
|
291
|
+
peek(params: {
|
|
292
|
+
client: Client
|
|
293
|
+
address: string
|
|
294
|
+
}): Promise<bigint | undefined>
|
|
290
295
|
};
|
|
291
296
|
/** Full chain descriptor used by clients and transports for network-aware operations. */
|
|
292
297
|
type StacksChain = {
|
|
@@ -287,6 +287,11 @@ type NonceManager = {
|
|
|
287
287
|
client: Client
|
|
288
288
|
address: string
|
|
289
289
|
}): void | Promise<void>
|
|
290
|
+
/** Next nonce that {@link NonceManager.consume} would return without consuming it, or `undefined` if untracked. */
|
|
291
|
+
peek(params: {
|
|
292
|
+
client: Client
|
|
293
|
+
address: string
|
|
294
|
+
}): Promise<bigint | undefined>
|
|
290
295
|
};
|
|
291
296
|
/** Full chain descriptor used by clients and transports for network-aware operations. */
|
|
292
297
|
type StacksChain = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ContractResponseError,
|
|
3
3
|
getContract
|
|
4
|
-
} from "../../shared/stacks-
|
|
4
|
+
} from "../../shared/stacks-k96f5g1a.js";
|
|
5
5
|
import {
|
|
6
6
|
deployContract,
|
|
7
7
|
getAccountInfo,
|
|
@@ -15,12 +15,12 @@ import {
|
|
|
15
15
|
simulateCall,
|
|
16
16
|
simulateTransaction,
|
|
17
17
|
transferStx
|
|
18
|
-
} from "../../shared/stacks-
|
|
18
|
+
} from "../../shared/stacks-a48qj7ss.js";
|
|
19
19
|
import {
|
|
20
20
|
callContract,
|
|
21
21
|
getMapEntry,
|
|
22
22
|
readContract
|
|
23
|
-
} from "../../shared/stacks-
|
|
23
|
+
} from "../../shared/stacks-txh6dadn.js";
|
|
24
24
|
import {
|
|
25
25
|
watchAddress,
|
|
26
26
|
watchAddressBalance,
|
|
@@ -31,13 +31,13 @@ import {
|
|
|
31
31
|
} from "../../shared/stacks-tjjnkt3r.js";
|
|
32
32
|
import {
|
|
33
33
|
sponsorTransaction
|
|
34
|
-
} from "../../shared/stacks-
|
|
34
|
+
} from "../../shared/stacks-s6v8k4j5.js";
|
|
35
35
|
import {
|
|
36
36
|
estimateFee,
|
|
37
37
|
getNonce,
|
|
38
38
|
isProviderAccount,
|
|
39
39
|
sendTransaction
|
|
40
|
-
} from "../../shared/stacks-
|
|
40
|
+
} from "../../shared/stacks-r3mqgw2d.js";
|
|
41
41
|
import"../../shared/stacks-709tra19.js";
|
|
42
42
|
import"../../shared/stacks-g7ffp8aj.js";
|
|
43
43
|
import"../../shared/stacks-wqgepfab.js";
|
package/dist/src/bns/index.d.cts
CHANGED
|
@@ -37,6 +37,11 @@ type NonceManager = {
|
|
|
37
37
|
client: Client
|
|
38
38
|
address: string
|
|
39
39
|
}): void | Promise<void>
|
|
40
|
+
/** Next nonce that {@link NonceManager.consume} would return without consuming it, or `undefined` if untracked. */
|
|
41
|
+
peek(params: {
|
|
42
|
+
client: Client
|
|
43
|
+
address: string
|
|
44
|
+
}): Promise<bigint | undefined>
|
|
40
45
|
};
|
|
41
46
|
/** Full chain descriptor used by clients and transports for network-aware operations. */
|
|
42
47
|
type StacksChain = {
|
package/dist/src/bns/index.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ type NonceManager = {
|
|
|
37
37
|
client: Client
|
|
38
38
|
address: string
|
|
39
39
|
}): void | Promise<void>
|
|
40
|
+
/** Next nonce that {@link NonceManager.consume} would return without consuming it, or `undefined` if untracked. */
|
|
41
|
+
peek(params: {
|
|
42
|
+
client: Client
|
|
43
|
+
address: string
|
|
44
|
+
}): Promise<bigint | undefined>
|
|
40
45
|
};
|
|
41
46
|
/** Full chain descriptor used by clients and transports for network-aware operations. */
|
|
42
47
|
type StacksChain = {
|
package/dist/src/bns/index.js
CHANGED
|
@@ -3,9 +3,9 @@ import"../../shared/stacks-kg1npstf.js";
|
|
|
3
3
|
import {
|
|
4
4
|
ContractResponseError,
|
|
5
5
|
getContract
|
|
6
|
-
} from "../../shared/stacks-
|
|
7
|
-
import"../../shared/stacks-
|
|
8
|
-
import"../../shared/stacks-
|
|
6
|
+
} from "../../shared/stacks-k96f5g1a.js";
|
|
7
|
+
import"../../shared/stacks-txh6dadn.js";
|
|
8
|
+
import"../../shared/stacks-r3mqgw2d.js";
|
|
9
9
|
import"../../shared/stacks-709tra19.js";
|
|
10
10
|
import"../../shared/stacks-g7ffp8aj.js";
|
|
11
11
|
import"../../shared/stacks-z53fwfcm.js";
|
package/dist/src/index.cjs
CHANGED
|
@@ -4,22 +4,28 @@ __export(exports_src, {
|
|
|
4
4
|
webSocket: () => webSocket,
|
|
5
5
|
walletActions: () => walletActions,
|
|
6
6
|
testnet: () => testnet,
|
|
7
|
+
startNonceReconciler: () => startNonceReconciler,
|
|
7
8
|
resolveNonce: () => resolveNonce,
|
|
8
9
|
redisStore: () => redisStore,
|
|
10
|
+
reconcileNonce: () => reconcileNonce,
|
|
9
11
|
publicActions: () => publicActions,
|
|
10
12
|
providerToAccount: () => providerToAccount,
|
|
11
13
|
postgresStore: () => postgresStore,
|
|
12
14
|
parseStx: () => parseStx,
|
|
13
15
|
on: () => exports_on,
|
|
16
|
+
nextFreeNonce: () => nextFreeNonce,
|
|
14
17
|
multisigActions: () => multisigActions,
|
|
15
18
|
mocknet: () => mocknet,
|
|
19
|
+
mempoolAwareSource: () => mempoolAwareSource,
|
|
16
20
|
memoryStore: () => memoryStore,
|
|
17
21
|
mainnet: () => mainnet,
|
|
18
22
|
jsonRpcSource: () => jsonRpcSource,
|
|
19
23
|
isValidAddress: () => isValidAddress,
|
|
20
24
|
isNonceConflictError: () => isNonceConflictError,
|
|
21
25
|
isAddressEqual: () => isAddressEqual,
|
|
26
|
+
indexSource: () => indexSource,
|
|
22
27
|
http: () => http,
|
|
28
|
+
hiroNonceSource: () => hiroNonceSource,
|
|
23
29
|
getContractAddress: () => getContractAddress,
|
|
24
30
|
formatStx: () => formatStx,
|
|
25
31
|
fallback: () => fallback,
|
|
@@ -266,6 +272,10 @@ function redisStore(params) {
|
|
|
266
272
|
},
|
|
267
273
|
async reset(key) {
|
|
268
274
|
await redis.send("DEL", [prefix + key]);
|
|
275
|
+
},
|
|
276
|
+
async peek(key) {
|
|
277
|
+
const v = await redis.send("GET", [prefix + key]);
|
|
278
|
+
return v == null ? undefined : BigInt(v);
|
|
269
279
|
}
|
|
270
280
|
};
|
|
271
281
|
}
|
|
@@ -296,6 +306,97 @@ function postgresStore(params) {
|
|
|
296
306
|
async reset(key) {
|
|
297
307
|
await ensureTable();
|
|
298
308
|
await sql`DELETE FROM stacks_nonce_state WHERE key = ${key}`;
|
|
309
|
+
},
|
|
310
|
+
async peek(key) {
|
|
311
|
+
await ensureTable();
|
|
312
|
+
const rows = await sql`SELECT next FROM stacks_nonce_state WHERE key = ${key}`;
|
|
313
|
+
const row = rows[0];
|
|
314
|
+
return row ? BigInt(String(row.next)) : undefined;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
// src/actions/wallet/nonceSources.ts
|
|
319
|
+
function resolveFetch(fetchImpl) {
|
|
320
|
+
const f = fetchImpl ?? globalThis.fetch;
|
|
321
|
+
if (!f)
|
|
322
|
+
throw new Error("No fetch implementation available; pass `fetchImpl`");
|
|
323
|
+
return f;
|
|
324
|
+
}
|
|
325
|
+
function nextFreeNonce(confirmed, pending) {
|
|
326
|
+
const taken = new Set(pending.filter((n2) => n2 >= confirmed).map((n2) => n2.toString()));
|
|
327
|
+
let n = confirmed;
|
|
328
|
+
while (taken.has(n.toString()))
|
|
329
|
+
n += 1n;
|
|
330
|
+
return n;
|
|
331
|
+
}
|
|
332
|
+
function mempoolAwareSource(params) {
|
|
333
|
+
const getConfirmed = params.getConfirmed ?? (({ client, address }) => getNonce(client, { address }));
|
|
334
|
+
return {
|
|
335
|
+
async get({ client, address }) {
|
|
336
|
+
const confirmed = await getConfirmed({ client, address });
|
|
337
|
+
let pending = [];
|
|
338
|
+
try {
|
|
339
|
+
pending = await params.getPending({ client, address });
|
|
340
|
+
} catch {
|
|
341
|
+
return confirmed;
|
|
342
|
+
}
|
|
343
|
+
return nextFreeNonce(confirmed, pending);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function indexSource(params = {}) {
|
|
348
|
+
const baseUrl = (params.baseUrl ?? "https://api.secondlayer.tools").replace(/\/$/, "");
|
|
349
|
+
const fetchImpl = resolveFetch(params.fetchImpl);
|
|
350
|
+
const maxPages = params.maxPages ?? 10;
|
|
351
|
+
return mempoolAwareSource({
|
|
352
|
+
getConfirmed: params.getConfirmed,
|
|
353
|
+
async getPending({ address }) {
|
|
354
|
+
const out = [];
|
|
355
|
+
let cursor;
|
|
356
|
+
let pages = 0;
|
|
357
|
+
do {
|
|
358
|
+
const url = new URL(`${baseUrl}/v1/index/mempool`);
|
|
359
|
+
url.searchParams.set("sender", address);
|
|
360
|
+
url.searchParams.set("limit", "200");
|
|
361
|
+
if (cursor)
|
|
362
|
+
url.searchParams.set("from_cursor", cursor);
|
|
363
|
+
const res = await fetchImpl(url, {
|
|
364
|
+
headers: params.apiKey ? { authorization: `Bearer ${params.apiKey}` } : undefined
|
|
365
|
+
});
|
|
366
|
+
if (!res.ok) {
|
|
367
|
+
throw new Error(`indexSource: /v1/index/mempool ${res.status}`);
|
|
368
|
+
}
|
|
369
|
+
const data = await res.json();
|
|
370
|
+
for (const tx of data.mempool ?? []) {
|
|
371
|
+
if (tx.nonce != null)
|
|
372
|
+
out.push(BigInt(tx.nonce));
|
|
373
|
+
}
|
|
374
|
+
cursor = data.next_cursor ?? undefined;
|
|
375
|
+
pages += 1;
|
|
376
|
+
} while (cursor && pages < maxPages);
|
|
377
|
+
if (cursor) {
|
|
378
|
+
console.warn(`indexSource: stopped paging mempool for ${address} after ${maxPages} pages; pending set may be incomplete`);
|
|
379
|
+
}
|
|
380
|
+
return out;
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function hiroNonceSource(params) {
|
|
385
|
+
const baseUrl = params.baseUrl.replace(/\/$/, "");
|
|
386
|
+
const fetchImpl = resolveFetch(params.fetchImpl);
|
|
387
|
+
return {
|
|
388
|
+
async get({ address }) {
|
|
389
|
+
const res = await fetchImpl(`${baseUrl}/extended/v1/address/${address}/nonces`, {
|
|
390
|
+
headers: params.apiKey ? { "x-api-key": params.apiKey } : undefined
|
|
391
|
+
});
|
|
392
|
+
if (!res.ok) {
|
|
393
|
+
throw new Error(`hiroNonceSource: /nonces ${res.status}`);
|
|
394
|
+
}
|
|
395
|
+
const data = await res.json();
|
|
396
|
+
const missing = (data.detected_missing_nonces ?? []).map((n) => BigInt(n)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
397
|
+
if (missing[0] !== undefined)
|
|
398
|
+
return missing[0];
|
|
399
|
+
return BigInt(data.possible_next_nonce ?? 0);
|
|
299
400
|
}
|
|
300
401
|
};
|
|
301
402
|
}
|
|
@@ -717,5 +818,5 @@ class ReadOnlyCallError extends BaseError {
|
|
|
717
818
|
name = "ReadOnlyCallError";
|
|
718
819
|
}
|
|
719
820
|
|
|
720
|
-
//# debugId=
|
|
821
|
+
//# debugId=FF2669570662824964756E2164756E21
|
|
721
822
|
//# sourceMappingURL=index.cjs.map
|