@chironbuilder/ootle-sdk 0.1.8 → 0.1.10
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/dist/accountApi.d.ts +7 -2
- package/dist/accountApi.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/wallet.d.ts +124 -10
- package/dist/wallet.d.ts.map +1 -1
- package/dist/wallet.js +417 -129
- package/package.json +12 -3
package/dist/accountApi.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { IndexerProvider } from "@tari-project/ootle-indexer";
|
|
2
2
|
import type { Instruction, SubstateRequirement } from "@tari-project/ootle-ts-bindings";
|
|
3
|
-
import type { TokenBalance } from "./wallet.js";
|
|
3
|
+
import type { FeeType, TokenBalance } from "./wallet.js";
|
|
4
4
|
export interface TransactionExecuteOpts {
|
|
5
5
|
maxFee?: bigint;
|
|
6
6
|
dryRun?: boolean;
|
|
7
7
|
inputs?: SubstateRequirement[];
|
|
8
8
|
maxRetries?: number;
|
|
9
|
+
/** See `FeeType`'s own doc comment. `OotleAccount` honors this; a `DaemonAccount`
|
|
10
|
+
* (walletd-relayed) implementation of this interface must implement its own support or
|
|
11
|
+
* explicitly reject `{ kind: "private" }` -- there is no default private-fee behavior a shared
|
|
12
|
+
* interface can provide for a backend this package doesn't talk to. */
|
|
13
|
+
feeType?: FeeType;
|
|
9
14
|
}
|
|
10
15
|
/**
|
|
11
16
|
* Shared surface both `OotleAccount` (this extension's own seed-derived signer) and `DaemonAccount`
|
|
@@ -27,7 +32,7 @@ export interface WalletAccountApi {
|
|
|
27
32
|
* requires the owner public key the wallet address encodes; a bare component address never
|
|
28
33
|
* reveals it, so that form can only pay an account that already exists). Prefer asking senders
|
|
29
34
|
* for the wallet address — it's the one form that always works. */
|
|
30
|
-
send(recipientAddress: string, resourceAddress: string, amount: bigint, maxFee?: bigint): Promise<unknown>;
|
|
35
|
+
send(recipientAddress: string, resourceAddress: string, amount: bigint, maxFee?: bigint, feeType?: FeeType): Promise<unknown>;
|
|
31
36
|
claimTestnetXtr(): Promise<unknown>;
|
|
32
37
|
}
|
|
33
38
|
//# sourceMappingURL=accountApi.d.ts.map
|
package/dist/accountApi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accountApi.d.ts","sourceRoot":"","sources":["../src/accountApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"accountApi.d.ts","sourceRoot":"","sources":["../src/accountApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACxF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;2EAGuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACvC;2FACuF;IACvF,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACxC,OAAO,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtF;;;;;uEAKmE;IACnE,IAAI,CAAC,gBAAgB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9H,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACrC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,6 @@ export { withTimeout } from "./timeout.js";
|
|
|
16
16
|
export type { KeyValueStore, PendingShield, ShieldedOutputRecord } from "./storage.js";
|
|
17
17
|
export { addPendingShield, addShieldedOutput, configureOotleStorage, getKnownVersions, getPrivatePaymentScanCursor, listPendingShields, listShieldedOutputs, localAccountId, markShieldedOutputSpent, removePendingShield, serialized, setKnownVersions, setPrivatePaymentScanCursor, wipeOotleState, } from "./storage.js";
|
|
18
18
|
export { chromeStorageAdapter, inMemoryAdapter, localStorageAdapter } from "./adapters.js";
|
|
19
|
-
export type { PrivateBalance, TokenBalance } from "./wallet.js";
|
|
20
|
-
export { OotleAccount, assertValidMinimumValuePromise, buildHtlcSpendStatement, extractMissingSubstateAddress, extractStaleLockVersion, forgetKnownVersions, pollTransactionResult, recoverPendingShields, resetKnownVersions, resolveInputsWithRetry, resolveSendPrivatelyPlan, resolveUnshieldPlan, selectShieldedUtxosForAmount, selectUnspentShieldedOutputs, substateExists, summarizePrivateHoldings, synthesizeShieldedOnlyBalances, } from "./wallet.js";
|
|
19
|
+
export type { FeeType, PrivateBalance, TokenBalance } from "./wallet.js";
|
|
20
|
+
export { OotleAccount, assertValidMinimumValuePromise, buildHtlcSpendStatement, extractMissingSubstateAddress, extractStaleLockVersion, forgetKnownVersions, pollTransactionResult, recoverPendingShields, resetKnownVersions, resolveInputsWithRetry, resolveSendPrivatelyPlan, resolveUnshieldPlan, selectPrivateFeeUtxo, selectShieldedUtxosForAmount, selectUnspentShieldedOutputs, substateExists, summarizePrivateHoldings, synthesizeShieldedOnlyBalances, } from "./wallet.js";
|
|
21
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEhF,OAAO,EACL,wBAAwB,EACxB,iCAAiC,EACjC,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,+BAA+B,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAEhG,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEhI,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,4BAA4B,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEzE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,oCAAoC,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAEpI,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACvF,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,2BAA2B,EAC3B,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,uBAAuB,EACvB,mBAAmB,EACnB,UAAU,EACV,gBAAgB,EAChB,2BAA2B,EAC3B,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAE3F,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEhF,OAAO,EACL,wBAAwB,EACxB,iCAAiC,EACjC,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,+BAA+B,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAEhG,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEhI,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,4BAA4B,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEzE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,oCAAoC,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAEpI,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACvF,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,2BAA2B,EAC3B,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,uBAAuB,EACvB,mBAAmB,EACnB,UAAU,EACV,gBAAgB,EAChB,2BAA2B,EAC3B,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAE3F,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EACL,YAAY,EACZ,8BAA8B,EAC9B,uBAAuB,EACvB,6BAA6B,EAC7B,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,cAAc,EACd,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11,4 +11,4 @@ export { decryptVault, encryptVault, fromHex, toHex } from "./vault.js";
|
|
|
11
11
|
export { withTimeout } from "./timeout.js";
|
|
12
12
|
export { addPendingShield, addShieldedOutput, configureOotleStorage, getKnownVersions, getPrivatePaymentScanCursor, listPendingShields, listShieldedOutputs, localAccountId, markShieldedOutputSpent, removePendingShield, serialized, setKnownVersions, setPrivatePaymentScanCursor, wipeOotleState, } from "./storage.js";
|
|
13
13
|
export { chromeStorageAdapter, inMemoryAdapter, localStorageAdapter } from "./adapters.js";
|
|
14
|
-
export { OotleAccount, assertValidMinimumValuePromise, buildHtlcSpendStatement, extractMissingSubstateAddress, extractStaleLockVersion, forgetKnownVersions, pollTransactionResult, recoverPendingShields, resetKnownVersions, resolveInputsWithRetry, resolveSendPrivatelyPlan, resolveUnshieldPlan, selectShieldedUtxosForAmount, selectUnspentShieldedOutputs, substateExists, summarizePrivateHoldings, synthesizeShieldedOnlyBalances, } from "./wallet.js";
|
|
14
|
+
export { OotleAccount, assertValidMinimumValuePromise, buildHtlcSpendStatement, extractMissingSubstateAddress, extractStaleLockVersion, forgetKnownVersions, pollTransactionResult, recoverPendingShields, resetKnownVersions, resolveInputsWithRetry, resolveSendPrivatelyPlan, resolveUnshieldPlan, selectPrivateFeeUtxo, selectShieldedUtxosForAmount, selectUnspentShieldedOutputs, substateExists, summarizePrivateHoldings, synthesizeShieldedOnlyBalances, } from "./wallet.js";
|
package/dist/wallet.d.ts
CHANGED
|
@@ -6,6 +6,16 @@ import type { WalletAccountApi } from "./accountApi.js";
|
|
|
6
6
|
import type { ScannedStealthOutput } from "./confidential.js";
|
|
7
7
|
import { type NetworkName } from "./ootleNetwork.js";
|
|
8
8
|
import type { ShieldedOutputRecord } from "./storage.js";
|
|
9
|
+
/** How a transaction's fee is paid. Defaults to `transparent` everywhere (unchanged behavior:
|
|
10
|
+
* revealed balance, reveals the paying account on-chain). `private` instead spends a stealth
|
|
11
|
+
* UTXO of `feeResourceAddress` -- see `OotleAccount.buildPrivateFeeInstructions`'s doc comment
|
|
12
|
+
* for why this is single-UTXO only, and `selectPrivateFeeUtxo` for how one is chosen. */
|
|
13
|
+
export type FeeType = {
|
|
14
|
+
kind: "transparent";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "private";
|
|
17
|
+
feeResourceAddress: string;
|
|
18
|
+
};
|
|
9
19
|
export interface TokenBalance {
|
|
10
20
|
resourceAddress: string;
|
|
11
21
|
kind: string;
|
|
@@ -129,10 +139,21 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
129
139
|
dryRun?: boolean;
|
|
130
140
|
inputs?: SubstateRequirement[];
|
|
131
141
|
maxRetries?: number;
|
|
142
|
+
/**
|
|
143
|
+
* Defaults to `{ kind: "transparent" }` (unchanged behavior: fee paid from the account's
|
|
144
|
+
* revealed balance, which reveals `account` on-chain). `{ kind: "private" }` instead pays
|
|
145
|
+
* from a shielded UTXO of `feeResourceAddress`, auto-selected via `selectPrivateFeeUtxo`
|
|
146
|
+
* (smallest unspent record that alone covers `maxFee` -- shield some first if none
|
|
147
|
+
* qualifies) -- see `buildPrivateFeeInstructions`'s doc comment for why this is
|
|
148
|
+
* single-UTXO only. `dryRun` never touches local storage even when private.
|
|
149
|
+
*/
|
|
150
|
+
feeType?: FeeType;
|
|
132
151
|
}): Promise<{
|
|
133
152
|
transaction_id: TransactionId;
|
|
134
153
|
result: ExecuteResult;
|
|
135
154
|
} | (IndexerGetTransactionResultResponse & {
|
|
155
|
+
transaction_id: TransactionId;
|
|
156
|
+
} & {
|
|
136
157
|
transactionId?: string;
|
|
137
158
|
})>;
|
|
138
159
|
/**
|
|
@@ -191,10 +212,12 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
191
212
|
* never reveals the owner key `CreateAccount` needs, so that form can only pay an account that
|
|
192
213
|
* already exists.
|
|
193
214
|
*/
|
|
194
|
-
send(recipientAddress: string, resourceAddress: string, amount: bigint, maxFee?: bigint): Promise<{
|
|
215
|
+
send(recipientAddress: string, resourceAddress: string, amount: bigint, maxFee?: bigint, feeType?: FeeType): Promise<{
|
|
195
216
|
transaction_id: TransactionId;
|
|
196
217
|
result: ExecuteResult;
|
|
197
218
|
} | (IndexerGetTransactionResultResponse & {
|
|
219
|
+
transaction_id: TransactionId;
|
|
220
|
+
} & {
|
|
198
221
|
transactionId?: string;
|
|
199
222
|
})>;
|
|
200
223
|
/**
|
|
@@ -227,10 +250,12 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
227
250
|
* produces a bucket holding the one new confidential output, which `deposit` puts back in the
|
|
228
251
|
* same vault.
|
|
229
252
|
*/
|
|
230
|
-
depositConfidential(resourceAddress: string, amount: bigint, maxFee?: bigint): Promise<{
|
|
253
|
+
depositConfidential(resourceAddress: string, amount: bigint, maxFee?: bigint, feeType?: FeeType): Promise<{
|
|
231
254
|
transaction_id: TransactionId;
|
|
232
255
|
result: ExecuteResult;
|
|
233
256
|
} | (IndexerGetTransactionResultResponse & {
|
|
257
|
+
transaction_id: TransactionId;
|
|
258
|
+
} & {
|
|
234
259
|
transactionId?: string;
|
|
235
260
|
})>;
|
|
236
261
|
/**
|
|
@@ -287,7 +312,7 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
287
312
|
* erring high just wastes nothing; erring low burns the small fee-intent cost with nothing to
|
|
288
313
|
* show for it, since only the fee half of the transaction gets committed.
|
|
289
314
|
*/
|
|
290
|
-
shield(resourceAddress: string, amount: bigint, maxFee?: bigint, memo?: string, minimumValuePromise?: bigint): Promise<{
|
|
315
|
+
shield(resourceAddress: string, amount: bigint, maxFee?: bigint, memo?: string, minimumValuePromise?: bigint, feeType?: FeeType): Promise<{
|
|
291
316
|
transactionId: string;
|
|
292
317
|
commitment: string;
|
|
293
318
|
substateId: string;
|
|
@@ -360,7 +385,7 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
360
385
|
* mark the spent ones, even if the service worker dies between finalization and the storage
|
|
361
386
|
* writes.
|
|
362
387
|
*/
|
|
363
|
-
unshield(resourceAddress: string, revealedOutAmount: bigint, maxFee?: bigint, memo?: string): Promise<{
|
|
388
|
+
unshield(resourceAddress: string, revealedOutAmount: bigint, maxFee?: bigint, memo?: string, feeType?: FeeType): Promise<{
|
|
364
389
|
transactionId: string;
|
|
365
390
|
}>;
|
|
366
391
|
/**
|
|
@@ -390,7 +415,7 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
390
415
|
* substate with `SubstateNotFound`. Each is registered by address, plus every vault its own
|
|
391
416
|
* state references (the same vault-discovery this account's own address already gets).
|
|
392
417
|
*/
|
|
393
|
-
withdrawStealthAndExecute(resourceAddress: string, amount: bigint, workspaceVarName: string, followUpInstructions: Instruction[], relatedComponents?: string[], maxFee?: bigint): Promise<{
|
|
418
|
+
withdrawStealthAndExecute(resourceAddress: string, amount: bigint, workspaceVarName: string, followUpInstructions: Instruction[], relatedComponents?: string[], maxFee?: bigint, feeType?: FeeType): Promise<{
|
|
394
419
|
transactionId: string;
|
|
395
420
|
}>;
|
|
396
421
|
/**
|
|
@@ -421,7 +446,7 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
421
446
|
* @param relatedComponents Every *other* component `followUpInstructions` touches — same
|
|
422
447
|
* reasoning as `withdrawStealthAndExecute`'s own parameter of the same name.
|
|
423
448
|
*/
|
|
424
|
-
redeemStealthOutputAndExecute(resourceAddress: string, commitmentHex: string, revealedAmount: bigint, followUpInstructions: Instruction[], relatedComponents?: string[], maxFee?: bigint): Promise<{
|
|
449
|
+
redeemStealthOutputAndExecute(resourceAddress: string, commitmentHex: string, revealedAmount: bigint, followUpInstructions: Instruction[], relatedComponents?: string[], maxFee?: bigint, feeType?: FeeType): Promise<{
|
|
425
450
|
transactionId: string;
|
|
426
451
|
}>;
|
|
427
452
|
/**
|
|
@@ -467,6 +492,87 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
467
492
|
* (which records its own outputs locally) — callers doing several of these in sequence must
|
|
468
493
|
* thread this value into the next call's `feeCommitmentHex` themselves.
|
|
469
494
|
*/
|
|
495
|
+
/**
|
|
496
|
+
* Builds the fee-payment instructions/inputs for spending a single stealth UTXO of
|
|
497
|
+
* `feeResourceAddress` to cover `maxFee`, plus the one-time `Signer` that must co-sign the
|
|
498
|
+
* resulting `StealthTransfer` fee spend (an ordinary account signature can't authorize a
|
|
499
|
+
* stealth spend -- see `redeemStealthOutputWithPrivateFee`'s doc comment, which this method's
|
|
500
|
+
* fee-half logic was extracted from verbatim). The unspent remainder above `maxFee` becomes a
|
|
501
|
+
* new stealth output back to this account (`feeChangeCommitment`); this only builds
|
|
502
|
+
* instructions, it doesn't touch local storage -- the caller records the change output (e.g.
|
|
503
|
+
* via `recordKnownShieldedOutput`) and marks `feeCommitmentHex` spent once the transaction
|
|
504
|
+
* actually lands, exactly as `redeemStealthOutputWithPrivateFee` itself does inline.
|
|
505
|
+
*
|
|
506
|
+
* Deliberately single-UTXO only, like the method this was extracted from -- the only shape
|
|
507
|
+
* confirmed live. Aggregating several stealth inputs into one `StealthTransfer` fee spend
|
|
508
|
+
* isn't proven here: `unshield()`'s multi-commitment coin selection uses a different,
|
|
509
|
+
* higher-level signing pipeline (`WalletStealthAuthorizer`) that isn't available inside this
|
|
510
|
+
* raw `TransactionBuilder`/`resolveTransaction`/`signTransaction` pipeline. Callers needing
|
|
511
|
+
* more than one stealth input's worth of fee should pick the smallest single covering UTXO,
|
|
512
|
+
* not sum several.
|
|
513
|
+
*/
|
|
514
|
+
private buildPrivateFeeInstructions;
|
|
515
|
+
/**
|
|
516
|
+
* Wires a `buildPrivateFeeInstructions()` result into a `StealthTransfer` builder in place of
|
|
517
|
+
* `.payFeeFromRevealed(maxFee)` -- via the builder's `withBuilder` escape hatch, since the
|
|
518
|
+
* class has no first-class "pay fee from a stealth UTXO" method of its own.
|
|
519
|
+
*/
|
|
520
|
+
private attachPrivateFee;
|
|
521
|
+
/**
|
|
522
|
+
* Completes signing for a `StealthTransfer`-based operation whose fee was attached via
|
|
523
|
+
* `attachPrivateFee`: adds the fee UTXO's one-time authorization as an extra signature
|
|
524
|
+
* (`AuthorizedTransfer.addSignature`) alongside whatever `WalletStealthAuthorizer` already
|
|
525
|
+
* produced for the operation's own stealth inputs, then seals. Bound to the same seal public
|
|
526
|
+
* key `authorized` itself uses, so every signature verifies against the same tx hash (see
|
|
527
|
+
* `AuthorizedTransfer.seal`'s doc comment).
|
|
528
|
+
*/
|
|
529
|
+
private sealWithPrivateFee;
|
|
530
|
+
/**
|
|
531
|
+
* Shared bookkeeping after ANY operation (`execute()` included) whose fee was paid privately
|
|
532
|
+
* lands on-chain: records the fee UTXO's change output and marks the spent one, mirroring
|
|
533
|
+
* `shield()`/`unshield()`'s existing pattern for their own outputs. Never call this for a dry
|
|
534
|
+
* run or a failed submission.
|
|
535
|
+
*/
|
|
536
|
+
private recordPrivateFeeSpend;
|
|
537
|
+
/**
|
|
538
|
+
* Selects and builds everything needed to pay a transaction's fee privately, or returns `null`
|
|
539
|
+
* for `{ kind: "transparent" }` -- the single entry point every fee-paying method (`execute()`
|
|
540
|
+
* and every `StealthTransfer`-based one below) uses to interpret a `FeeType` the same way.
|
|
541
|
+
*/
|
|
542
|
+
private resolvePrivateFee;
|
|
543
|
+
/**
|
|
544
|
+
* Filters out unspent local records for `resourceAddress` that can't actually be read right now
|
|
545
|
+
* (checked largest-first, stopping once verified-readable records cover `targetAmount` -- the
|
|
546
|
+
* same greedy order `selectShieldedUtxosForAmount` itself uses, so this does the minimum
|
|
547
|
+
* verification needed rather than checking every record up front) -- used by `sendPrivately()`
|
|
548
|
+
* and `unshield()` before their own coin-selection plan runs, so a record that's actually
|
|
549
|
+
* already spent (confirmed live: the indexer can 500 instead of cleanly saying so, e.g. for one
|
|
550
|
+
* spent through another device/session sharing this seed) gets skipped as a *candidate* instead
|
|
551
|
+
* of failing the whole transaction deep inside the `StealthTransfer` builder with no way to
|
|
552
|
+
* retry excluding just the bad one.
|
|
553
|
+
*
|
|
554
|
+
* Deliberately never marks a failing record spent in local storage -- same reasoning as
|
|
555
|
+
* `resolvePrivateFee`: an unreadable-right-now record could just as easily be a transient
|
|
556
|
+
* indexer error as a genuinely spent output, and this wallet's only handle back to a real
|
|
557
|
+
* stealth output is this local record (see `ShieldedOutputRecord`'s own doc comment), so wrongly
|
|
558
|
+
* deleting one is unrecoverable. A record skipped here is simply not offered as a candidate for
|
|
559
|
+
* *this* attempt; it stays in local storage to be tried again later.
|
|
560
|
+
*/
|
|
561
|
+
private filterReadableShieldedOutputs;
|
|
562
|
+
/**
|
|
563
|
+
* Shared prepare/authorize/seal/submit path for every `StealthTransfer`-based operation
|
|
564
|
+
* (shield/unshield/withdrawStealthAndExecute/etc.): `builder` must already have every
|
|
565
|
+
* operation-specific call applied (`spendRevealedInput`, `toStealthOutput`,
|
|
566
|
+
* `spendStealthInput`, `andThen`, ...) EXCEPT the fee -- this attaches either
|
|
567
|
+
* `.payFeeFromRevealed(maxFee)` or a private fee (via `resolvePrivateFee`/`attachPrivateFee`),
|
|
568
|
+
* then authorizes with `WalletStealthAuthorizer` (passing `authorizerOpts` through, e.g.
|
|
569
|
+
* `viewSecret` when the operation itself spends stealth inputs), seals (co-signing the private
|
|
570
|
+
* fee's UTXO when there is one), and submits -- but does NOT poll to finality or record
|
|
571
|
+
* anything: callers keep their own existing `addPendingShield`/poll/`recordKnownShieldedOutput`
|
|
572
|
+
* logic for the operation's own effect, and must call `recordPrivateFeeSpend` themselves once
|
|
573
|
+
* `transactionId` actually lands (never for a dry run) using the returned `privateFee`.
|
|
574
|
+
*/
|
|
575
|
+
private prepareSignSubmit;
|
|
470
576
|
redeemStealthOutputWithPrivateFee(resourceAddress: string, commitmentHex: string, revealedAmount: bigint, followUpInstructions: Instruction[], feeResourceAddress: string, feeCommitmentHex: string, maxFee: bigint, relatedComponents?: string[]): Promise<{
|
|
471
577
|
transactionId: string;
|
|
472
578
|
feeChangeCommitment: string;
|
|
@@ -501,7 +607,7 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
501
607
|
* other stealth payment addressed to them — spend authority is governed entirely separately, by
|
|
502
608
|
* `payTo`.
|
|
503
609
|
*/
|
|
504
|
-
htlcFund(resourceAddress: string, amount: bigint, claimantWalletAddress: string, hashLockHex: string, refundEpoch: bigint, maxFee?: bigint): Promise<{
|
|
610
|
+
htlcFund(resourceAddress: string, amount: bigint, claimantWalletAddress: string, hashLockHex: string, refundEpoch: bigint, maxFee?: bigint, feeType?: FeeType): Promise<{
|
|
505
611
|
transactionId: string;
|
|
506
612
|
conditions: object[];
|
|
507
613
|
ownCommitment: string;
|
|
@@ -534,7 +640,7 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
534
640
|
* unblinded mask+value and the two witnesses below, so there is nothing left for the builder
|
|
535
641
|
* pipeline to add.
|
|
536
642
|
*/
|
|
537
|
-
htlcClaim(resourceAddress: string, commitmentHex: string, conditions: object[], preimageHex: string, maxFee?: bigint): Promise<{
|
|
643
|
+
htlcClaim(resourceAddress: string, commitmentHex: string, conditions: object[], preimageHex: string, maxFee?: bigint, feeType?: FeeType): Promise<{
|
|
538
644
|
transactionId: string;
|
|
539
645
|
}>;
|
|
540
646
|
/**
|
|
@@ -555,7 +661,7 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
555
661
|
* `buildStealthTransferStatement`-in-one-call approach, same bypass of
|
|
556
662
|
* `StealthTransfer`/`WalletStealthAuthorizer`.
|
|
557
663
|
*/
|
|
558
|
-
htlcRefund(resourceAddress: string, commitmentHex: string, conditions: object[], amount: bigint, outputMaskHex: string, maxFee?: bigint): Promise<{
|
|
664
|
+
htlcRefund(resourceAddress: string, commitmentHex: string, conditions: object[], amount: bigint, outputMaskHex: string, maxFee?: bigint, feeType?: FeeType): Promise<{
|
|
559
665
|
transactionId: string;
|
|
560
666
|
}>;
|
|
561
667
|
/**
|
|
@@ -585,7 +691,7 @@ export declare class OotleAccount implements WalletAccountApi {
|
|
|
585
691
|
* grabbing "the first new stealth output" would risk recording the recipient's payment as our
|
|
586
692
|
* own. See `PendingShield.ownCommitment`'s doc comment.
|
|
587
693
|
*/
|
|
588
|
-
sendPrivately(resourceAddress: string, recipientWalletAddress: string, amount: bigint, maxFee?: bigint, memo?: string, minimumValuePromise?: bigint): Promise<{
|
|
694
|
+
sendPrivately(resourceAddress: string, recipientWalletAddress: string, amount: bigint, maxFee?: bigint, memo?: string, minimumValuePromise?: bigint, feeType?: FeeType): Promise<{
|
|
589
695
|
transactionId: string;
|
|
590
696
|
recipientCommitment: string;
|
|
591
697
|
recipientSubstateId: string;
|
|
@@ -782,6 +888,14 @@ export declare function resolveUnshieldPlan(records: ShieldedOutputRecord[], res
|
|
|
782
888
|
commitments: string[];
|
|
783
889
|
remainder: bigint;
|
|
784
890
|
};
|
|
891
|
+
/**
|
|
892
|
+
* Pure planning for `execute()`'s private-fee path: picks the smallest unspent stealth UTXO of
|
|
893
|
+
* `feeResourceAddress` that alone covers `maxFee`. Deliberately the *smallest* qualifying record
|
|
894
|
+
* (not the largest, and never several combined) -- `buildPrivateFeeInstructions` only supports a
|
|
895
|
+
* single fee input (see its own doc comment for why), so this leaves larger shielded balances
|
|
896
|
+
* untouched for whatever they were shielded for rather than reaching for them first.
|
|
897
|
+
*/
|
|
898
|
+
export declare function selectPrivateFeeUtxo(records: ShieldedOutputRecord[], feeResourceAddress: string, maxFee: bigint, exclude?: string[]): ShieldedOutputRecord;
|
|
785
899
|
/**
|
|
786
900
|
* Pure planning for `sendPrivately()`: selects enough of the caller's unspent records for this
|
|
787
901
|
* resource to cover `amount` — unlike `resolveUnshieldPlan`, sending the *entire* selected total
|
package/dist/wallet.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,OAAO,EA2BR,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,aAAa,EACb,mCAAmC,EACnC,WAAW,EAIX,mBAAmB,EACnB,aAAa,EACd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAYxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAI9D,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAcrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAIzD,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf;;;wCAGoC;IACpC,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;kCAE8B;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC;yEACqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB;sEACkE;IAClE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;oDACgD;IAChD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;mGAE+F;IAC/F,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACtC;AAED;;;4CAG4C;AAC5C,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB;iCAC6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf;gFAC4E;IAC5E,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAmCD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CASpF;AAoCD;;;GAGG;AACH,qBAAa,YAAa,YAAW,gBAAgB;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAgC;IAKhD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IAEzC,OAAO;IAOP,+FAA+F;IAC/F,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,YAAY;IAOrF,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;IAqB7C;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;IAIzC;;;;;;;;OAQG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKtC,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IA+I5C;;;;;;;;;;;;;;;;OAgBG;IACG,OAAO,CACX,YAAY,EAAE,WAAW,EAAE,EAC3B,IAAI,GAAE;
|
|
1
|
+
{"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,OAAO,EA2BR,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,aAAa,EACb,mCAAmC,EACnC,WAAW,EAIX,mBAAmB,EACnB,aAAa,EACd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAYxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAI9D,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAcrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAIzD;;;yFAGyF;AACzF,MAAM,MAAM,OAAO,GAAG;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAAC;AAahG,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf;;;wCAGoC;IACpC,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;kCAE8B;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC;yEACqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB;sEACkE;IAClE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;oDACgD;IAChD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;mGAE+F;IAC/F,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACtC;AAED;;;4CAG4C;AAC5C,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB;iCAC6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf;gFAC4E;IAC5E,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAmCD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CASpF;AAoCD;;;GAGG;AACH,qBAAa,YAAa,YAAW,gBAAgB;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAgC;IAKhD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IAEzC,OAAO;IAOP,+FAA+F;IAC/F,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,YAAY;IAOrF,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;IAqB7C;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;IAIzC;;;;;;;;OAQG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKtC,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IA+I5C;;;;;;;;;;;;;;;;OAgBG;IACG,OAAO,CACX,YAAY,EAAE,WAAW,EAAE,EAC3B,IAAI,GAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;QAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB;;;;;;;WAOG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC;KACd;wBA8IqB,aAAa;gBAAU,aAAa;;wBA4DE,aAAa;;wBAksE4B,MAAM;;IApxElH;;;;;;;;;;;;;;;;;OAiBG;YACW,YAAY;IAgD1B;;;;;;;;;;;OAWG;YACW,UAAU;IAoCxB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,IAAI,CAAC,gBAAgB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAQ,EAAE,OAAO,GAAE,OAAiC;wBApH3G,aAAa;gBAAU,aAAa;;wBA4DE,aAAa;;wBAksE4B,MAAM;;IAxmElH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACG,mBAAmB,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,OAAO,GAAE,OAAiC;wBApLjG,aAAa;gBAAU,aAAa;;wBA4DE,aAAa;;wBAksE4B,MAAM;;IA7hElH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACG,eAAe,CAAC,MAAM,SAAQ,EAAE,OAAO,SAAK,EAAE,YAAY,SAAM;IA0EtE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,MAAM,CACV,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,MAAM,SAAS,EACf,IAAI,CAAC,EAAE,MAAM,EACb,mBAAmB,SAAK,EACxB,OAAO,GAAE,OAAiC,GACzC,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE,CAAC;IAkD1G;;;;;;;;;;;;;;;OAeG;IACG,kBAAkB,CACtB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IA4BzE;;;;;;;;;;;;;OAaG;IACG,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAOxH;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,QAAQ,CACZ,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,EACzB,MAAM,SAAU,EAChB,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,OAAiC,GACzC,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAqErC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,yBAAyB,CAC7B,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,oBAAoB,EAAE,WAAW,EAAE,EACnC,iBAAiB,GAAE,MAAM,EAAO,EAChC,MAAM,SAAU,EAChB,OAAO,GAAE,OAAiC,GACzC,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAsCrC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACG,6BAA6B,CACjC,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,oBAAoB,EAAE,WAAW,EAAE,EACnC,iBAAiB,GAAE,MAAM,EAAO,EAChC,MAAM,SAAU,EAChB,OAAO,GAAE,OAAiC,GACzC,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAmGrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0CG;IACH;;;;;;;;;;;;;;;;;;OAkBG;YACW,2BAA2B;IAgFzC;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;;;OAOG;YACW,kBAAkB;IAOhC;;;;;OAKG;YACW,qBAAqB;IAKnC;;;;OAIG;YACW,iBAAiB;IAmD/B;;;;;;;;;;;;;;;;;OAiBG;YACW,6BAA6B;IAkC3C;;;;;;;;;;;;OAYG;YACW,iBAAiB;IAsBzB,iCAAiC,CACrC,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,oBAAoB,EAAE,WAAW,EAAE,EACnC,kBAAkB,EAAE,MAAM,EAC1B,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,EACd,iBAAiB,GAAE,MAAM,EAAO,GAC/B,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE,CAAC;IAsFlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACG,QAAQ,CACZ,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,qBAAqB,EAAE,MAAM,EAC7B,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,MAAM,SAAS,EACf,OAAO,GAAE,OAAiC,GACzC,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAwCtG;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,SAAS,CACb,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAAE,EACpB,WAAW,EAAE,MAAM,EACnB,MAAM,SAAS,EACf,OAAO,GAAE,OAAiC,GACzC,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAmDrC;;;;;;;;;;;;;;;;;OAiBG;IACG,UAAU,CACd,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAAE,EACpB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,MAAM,SAAS,EACf,OAAO,GAAE,OAAiC,GACzC,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAuCrC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,aAAa,CACjB,eAAe,EAAE,MAAM,EACvB,sBAAsB,EAAE,MAAM,EAC9B,MAAM,EAAE,MAAM,EACd,MAAM,SAAU,EAChB,IAAI,CAAC,EAAE,MAAM,EACb,mBAAmB,SAAK,EACxB,OAAO,GAAE,OAAiC,GACzC,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE,CAAC;IA+E5H;;;;;;;;;OASG;IACG,mBAAmB,CAAC,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAqBrH;;;;;;;;OAQG;IACG,0BAA0B,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAI3F;;;;;;;;;;;;;;;OAeG;IACG,kBAAkB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAiBrD;;;;;;;;;;;;;;OAcG;IACG,sBAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,SAAK,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,oBAAoB,EAAE,CAAA;KAAE,CAAC;IA4D3H;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,oBAAoB,CACxB,eAAe,EAAE,MAAM,EACvB,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;KAAO,GAC7F,OAAO,CAAC,oBAAoB,EAAE,CAAC;CAoFnC;AAED;;;;;;;;;GASG;AACH;;;;;;;;;;;;;GAaG;AAIH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb;6DACyD;IACzD,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB,EAAE,MAAM,CAAC;IACjC,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,CA2BT;AAuED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,EAC5B,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACvC,sBAAsB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3C,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EAC5C,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GACzC,YAAY,EAAE,CAiBhB;AAoBD;;;;;;;;;;;GAWG;AACH;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAI9H;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,oBAAoB,EAAE,GAC9B;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EAAE,CASpE;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,oBAAoB,EAAE,EAC/B,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,GACnB;IAAE,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,oBAAoB,EAAE,CAAA;CAAE,CAiBzF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,GACxB;IAAE,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAmB9C;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,kBAAkB,EAAE,MAAM,EAC1B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,MAAM,EAAO,GACrB,oBAAoB,CAetB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GACb;IAAE,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAOjD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCpF;AAyGD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAM9E;AAED;;0GAE0G;AAC1G,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,eAAe,EACzB,aAAa,EAAE,MAAM,EACrB,SAAS,SAAS,GACjB,OAAO,CAAC,mCAAmC,CAAC,CAwB9C;AAmBD,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,eAAe,EACzB,YAAY,EAAE,mBAAmB,EAAE,EACnC,OAAO,SAAI,EACX,YAAY,SAAM,GACjB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAUhC;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQpG;AAmBD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5E;AAOD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGvG"}
|
package/dist/wallet.js
CHANGED
|
@@ -320,6 +320,7 @@ export class OotleAccount {
|
|
|
320
320
|
const provider = await this.getProvider();
|
|
321
321
|
const account = await this.getComponentAddress();
|
|
322
322
|
const maxFee = opts.maxFee ?? 5000n;
|
|
323
|
+
const feeType = opts.feeType ?? { kind: "transparent" };
|
|
323
324
|
// Resolved once, outside the retry loop below: every transaction now carries a mandatory
|
|
324
325
|
// `max_epoch`, and a fresh chain-tip lookup on every retry would buy nothing (the loop's
|
|
325
326
|
// whole retry budget runs in well under an epoch in practice) at the cost of an extra
|
|
@@ -335,18 +336,34 @@ export class OotleAccount {
|
|
|
335
336
|
const maxRetries = opts.maxRetries ?? 30;
|
|
336
337
|
const seenAddresses = new Set();
|
|
337
338
|
let inputs = await applyKnownVersions(opts.inputs ? [...opts.inputs] : []);
|
|
339
|
+
// Built once, outside the retry loop, like `maxEpoch` above: the fee stealth proof doesn't
|
|
340
|
+
// depend on which main-instruction substates a given attempt discovers, so redoing the
|
|
341
|
+
// indexer round trip + proof generation on every retry would buy nothing.
|
|
342
|
+
const accountId = localAccountId(this.index);
|
|
343
|
+
const privateFee = await this.resolvePrivateFee(feeType, maxFee);
|
|
338
344
|
for (let attempt = 0;; attempt++) {
|
|
339
|
-
const builder = TransactionBuilder.new(this.network, maxEpoch)
|
|
340
|
-
|
|
341
|
-
|
|
345
|
+
const builder = TransactionBuilder.new(this.network, maxEpoch).withInstructions(instructions);
|
|
346
|
+
if (privateFee) {
|
|
347
|
+
for (const instr of privateFee.feeInstructions)
|
|
348
|
+
builder.addFeeInstruction(instr);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
builder.feeTransactionPayFromComponent(account, maxFee);
|
|
352
|
+
}
|
|
342
353
|
if (inputs.length)
|
|
343
354
|
builder.withInputs(inputs);
|
|
355
|
+
if (privateFee)
|
|
356
|
+
builder.withInputs(privateFee.feeInputs);
|
|
344
357
|
const unsignedTx = builder.buildUnsignedTransaction();
|
|
345
358
|
try {
|
|
359
|
+
const extraSigners = privateFee ? [privateFee.feeSigner] : [];
|
|
346
360
|
if (opts.dryRun)
|
|
347
|
-
return await withTimeout(this.submitDryRun(provider, unsignedTx), 30_000, "submitting the transaction");
|
|
348
|
-
const result = await withTimeout(this.submitReal(provider, unsignedTx), 60_000, "submitting the transaction");
|
|
361
|
+
return await withTimeout(this.submitDryRun(provider, unsignedTx, extraSigners), 30_000, "submitting the transaction");
|
|
362
|
+
const result = await withTimeout(this.submitReal(provider, unsignedTx, extraSigners), 60_000, "submitting the transaction");
|
|
349
363
|
await recordKnownVersions(result);
|
|
364
|
+
if (privateFee && feeType.kind === "private") {
|
|
365
|
+
await this.recordPrivateFeeSpend(accountId, feeType.feeResourceAddress, privateFee, String(result.transaction_id));
|
|
366
|
+
}
|
|
350
367
|
return withTransactionId(result);
|
|
351
368
|
}
|
|
352
369
|
catch (e) {
|
|
@@ -431,9 +448,9 @@ export class OotleAccount {
|
|
|
431
448
|
* (only the real-submission response, which no longer carries a `result` field at all as of
|
|
432
449
|
* the 0.39.0 protocol upgrade).
|
|
433
450
|
*/
|
|
434
|
-
async submitDryRun(provider, unsignedTx) {
|
|
451
|
+
async submitDryRun(provider, unsignedTx, extraSigners = []) {
|
|
435
452
|
const resolved = await resolveTransaction(provider, { ...unsignedTx, dry_run: true });
|
|
436
|
-
const signed = await signTransaction([this.signer], resolved);
|
|
453
|
+
const signed = await signTransaction([this.signer, ...extraSigners], resolved);
|
|
437
454
|
const envelope = sealTransaction(signed);
|
|
438
455
|
const res = await fetch(`${defaultIndexerUrl(this.network)}/transactions/dry-run`, {
|
|
439
456
|
method: "POST",
|
|
@@ -486,7 +503,7 @@ export class OotleAccount {
|
|
|
486
503
|
* proven correct; only the result-polling and error-message construction are reimplemented, in
|
|
487
504
|
* the same detailed shape `submitDryRun()` above already produces.
|
|
488
505
|
*/
|
|
489
|
-
async submitReal(provider, unsignedTx) {
|
|
506
|
+
async submitReal(provider, unsignedTx, extraSigners = []) {
|
|
490
507
|
const resolved = await resolveTransaction(provider, unsignedTx);
|
|
491
508
|
// Both of these parse the transaction inside the wasm as an untagged enum
|
|
492
509
|
// (`ootle_wasm/core/src/transaction.rs`), so a single bad field anywhere collapses into "data
|
|
@@ -497,7 +514,7 @@ export class OotleAccount {
|
|
|
497
514
|
// is attached here rather than guessed at afterwards.
|
|
498
515
|
let signed;
|
|
499
516
|
try {
|
|
500
|
-
signed = await signTransaction([this.signer], resolved);
|
|
517
|
+
signed = await signTransaction([this.signer, ...extraSigners], resolved);
|
|
501
518
|
}
|
|
502
519
|
catch (e) {
|
|
503
520
|
throw new Error(`${e instanceof Error ? e.message : String(e)} — at signTransaction; resolved tx: ${describeResolvedTx(resolved)}`, { cause: e });
|
|
@@ -510,7 +527,8 @@ export class OotleAccount {
|
|
|
510
527
|
throw new Error(`${e instanceof Error ? e.message : String(e)} — at sealTransaction; signed: ${describeSignedTx(signed)}`, { cause: e });
|
|
511
528
|
}
|
|
512
529
|
const { transaction_id } = await provider.submitTransaction(envelope);
|
|
513
|
-
|
|
530
|
+
const result = await pollTransactionResult(provider, transaction_id);
|
|
531
|
+
return { ...result, transaction_id };
|
|
514
532
|
}
|
|
515
533
|
/**
|
|
516
534
|
* Transfers `amount` (raw, resource-native units) of `resourceAddress` from this account to
|
|
@@ -536,7 +554,7 @@ export class OotleAccount {
|
|
|
536
554
|
* never reveals the owner key `CreateAccount` needs, so that form can only pay an account that
|
|
537
555
|
* already exists.
|
|
538
556
|
*/
|
|
539
|
-
async send(recipientAddress, resourceAddress, amount, maxFee = 5000n) {
|
|
557
|
+
async send(recipientAddress, resourceAddress, amount, maxFee = 5000n, feeType = { kind: "transparent" }) {
|
|
540
558
|
const account = await this.getComponentAddress();
|
|
541
559
|
const isWalletAddress = recipientAddress.startsWith("otl");
|
|
542
560
|
const recipient = isWalletAddress ? componentAddressFromWalletAddress(recipientAddress) : recipientAddress;
|
|
@@ -562,7 +580,7 @@ export class OotleAccount {
|
|
|
562
580
|
}
|
|
563
581
|
}
|
|
564
582
|
instructions.push({ CallMethod: { call: { Address: account }, method: "withdraw", args: [resourceAddressLiteral(resourceAddress), amountLiteral(amount)] } }, { PutLastInstructionOutputOnWorkspace: { key: 0 } }, { CallMethod: { call: { Address: recipient }, method: "deposit", args: [{ Workspace: { id: 0, offset: null } }] } });
|
|
565
|
-
return this.execute(instructions, { maxFee });
|
|
583
|
+
return this.execute(instructions, { maxFee, feeType });
|
|
566
584
|
}
|
|
567
585
|
/**
|
|
568
586
|
* Moves `amount` of this account's own REVEALED balance into a Confidential-type vault for the
|
|
@@ -594,7 +612,7 @@ export class OotleAccount {
|
|
|
594
612
|
* produces a bucket holding the one new confidential output, which `deposit` puts back in the
|
|
595
613
|
* same vault.
|
|
596
614
|
*/
|
|
597
|
-
async depositConfidential(resourceAddress, amount, maxFee = 50000n) {
|
|
615
|
+
async depositConfidential(resourceAddress, amount, maxFee = 50000n, feeType = { kind: "transparent" }) {
|
|
598
616
|
if (amount <= 0n)
|
|
599
617
|
throw new Error(`depositConfidential: amount must be greater than zero, got ${amount}`);
|
|
600
618
|
const account = await this.getComponentAddress();
|
|
@@ -621,7 +639,7 @@ export class OotleAccount {
|
|
|
621
639
|
{ PutLastInstructionOutputOnWorkspace: { key: 0 } },
|
|
622
640
|
{ CallMethod: { call: { Address: account }, method: "deposit", args: [{ Workspace: { id: 0, offset: null } }] } },
|
|
623
641
|
];
|
|
624
|
-
return this.execute(instructions, { maxFee });
|
|
642
|
+
return this.execute(instructions, { maxFee, feeType });
|
|
625
643
|
}
|
|
626
644
|
/**
|
|
627
645
|
* Claims free testnet XTR from the network's builtin faucet (esmeralda/igor only — there is no
|
|
@@ -739,7 +757,7 @@ export class OotleAccount {
|
|
|
739
757
|
* erring high just wastes nothing; erring low burns the small fee-intent cost with nothing to
|
|
740
758
|
* show for it, since only the fee half of the transaction gets committed.
|
|
741
759
|
*/
|
|
742
|
-
async shield(resourceAddress, amount, maxFee = 50000n, memo, minimumValuePromise = 0n) {
|
|
760
|
+
async shield(resourceAddress, amount, maxFee = 50000n, memo, minimumValuePromise = 0n, feeType = { kind: "transparent" }) {
|
|
743
761
|
assertValidMinimumValuePromise(minimumValuePromise, amount);
|
|
744
762
|
const accountId = localAccountId(this.index);
|
|
745
763
|
const provider = await this.getProvider();
|
|
@@ -748,7 +766,7 @@ export class OotleAccount {
|
|
|
748
766
|
// on-chain component address -- passing `account` here throws "Bech32 decode error" (confirmed
|
|
749
767
|
// live). See getWalletAddress()'s doc comment for why these two addresses are easy to conflate.
|
|
750
768
|
const walletAddress = await this.getWalletAddress();
|
|
751
|
-
const
|
|
769
|
+
const builder = new StealthTransfer(provider, resourceAddress)
|
|
752
770
|
// StealthTransfer.prepare() auto-adds the revealed account's own substate and any vault
|
|
753
771
|
// addresses embedded in its on-chain state, but never the resource's own substate -- fine
|
|
754
772
|
// for XTR (resource_0101...0101 is engine-special-cased and needs no lock), but any other
|
|
@@ -757,24 +775,25 @@ export class OotleAccount {
|
|
|
757
775
|
// source (node_modules/@tari-project/ootle/dist/index.js) -- not assumed from the docs.
|
|
758
776
|
.withBuilder((b) => b.addInput({ substate_id: resourceAddress, version: null }))
|
|
759
777
|
.spendRevealedInput(account, amount)
|
|
760
|
-
.toStealthOutput(createOutput({ destination: walletAddress, amount, resourceAddress, memo: toMemo(memo), minimumValuePromise }))
|
|
761
|
-
|
|
762
|
-
|
|
778
|
+
.toStealthOutput(createOutput({ destination: walletAddress, amount, resourceAddress, memo: toMemo(memo), minimumValuePromise }));
|
|
779
|
+
// No stealth inputs to unblind for a shield's own transfer (revealed-only source), so no
|
|
780
|
+
// viewSecret needed here even when the FEE is paid privately -- `prepareSignSubmit` handles
|
|
781
|
+
// that UTXO's own decryption internally. `fromSpec`'s own default crypto is
|
|
782
|
+
// `new WasmStealthCrypto()` -- Network.LocalNet, NOT this account's real network -- which
|
|
783
|
+
// produced an "Invalid transaction signature" server-side rejection (confirmed live) since
|
|
784
|
+
// the balance proof it computes is network-domain-separated. Must match the network
|
|
785
|
+
// StealthTransfer itself used when it built the outputs statement (`prepareSignSubmit`
|
|
786
|
+
// already passes `this.network` for exactly this reason).
|
|
787
|
+
const { transactionId, spec, privateFee } = await this.prepareSignSubmit(builder, provider, account, maxFee, feeType);
|
|
763
788
|
const ownCommitment = extractOutputCommitment(spec, 0);
|
|
764
|
-
const wallet = new OotleWallet().registerKeyProvider(account, this.signer).setDefaultSigner(account);
|
|
765
|
-
// No stealth inputs to unblind for a shield (revealed-only source), so no viewSecret needed.
|
|
766
|
-
// `fromSpec`'s own default crypto is `new WasmStealthCrypto()` -- Network.LocalNet, NOT this
|
|
767
|
-
// account's real network -- which produced an "Invalid transaction signature" server-side
|
|
768
|
-
// rejection (confirmed live) since the balance proof it computes is network-domain-separated.
|
|
769
|
-
// Must match the network StealthTransfer itself used when it built the outputs statement.
|
|
770
|
-
const authorized = await WalletStealthAuthorizer.fromSpec(wallet, spec, { crypto: new WasmStealthCrypto(this.network) }).prepare(provider);
|
|
771
|
-
const envelope = await authorized.seal();
|
|
772
|
-
const transactionId = await submitTransaction(provider, envelope);
|
|
773
789
|
await addPendingShield({ transactionId, accountId, resourceAddress, amount: amount.toString(), ownCommitment, memo });
|
|
774
790
|
try {
|
|
775
791
|
const response = await withTimeout(pollTransactionResult(provider, transactionId), 60_000, "submitting the shield transaction");
|
|
776
792
|
await recordKnownVersions(response);
|
|
777
793
|
await recordKnownShieldedOutput(accountId, resourceAddress, ownCommitment, amount, transactionId, memo);
|
|
794
|
+
if (privateFee && feeType.kind === "private") {
|
|
795
|
+
await this.recordPrivateFeeSpend(accountId, feeType.feeResourceAddress, privateFee, transactionId);
|
|
796
|
+
}
|
|
778
797
|
}
|
|
779
798
|
finally {
|
|
780
799
|
await removePendingShield(transactionId);
|
|
@@ -876,9 +895,9 @@ export class OotleAccount {
|
|
|
876
895
|
* mark the spent ones, even if the service worker dies between finalization and the storage
|
|
877
896
|
* writes.
|
|
878
897
|
*/
|
|
879
|
-
async unshield(resourceAddress, revealedOutAmount, maxFee = 100000n, memo) {
|
|
898
|
+
async unshield(resourceAddress, revealedOutAmount, maxFee = 100000n, memo, feeType = { kind: "transparent" }) {
|
|
880
899
|
const accountId = localAccountId(this.index);
|
|
881
|
-
const records = await listShieldedOutputs(accountId);
|
|
900
|
+
const records = await this.filterReadableShieldedOutputs(await listShieldedOutputs(accountId), resourceAddress, revealedOutAmount);
|
|
882
901
|
const { commitments, remainder } = resolveUnshieldPlan(records, resourceAddress, revealedOutAmount);
|
|
883
902
|
const dust = 1n;
|
|
884
903
|
const provider = await this.getProvider();
|
|
@@ -894,18 +913,16 @@ export class OotleAccount {
|
|
|
894
913
|
for (const commitment of commitments) {
|
|
895
914
|
builder = builder.spendStealthInput(account, fromHex(commitment));
|
|
896
915
|
}
|
|
897
|
-
|
|
916
|
+
builder = builder
|
|
898
917
|
.toStealthOutput(createOutput({ destination: walletAddress, amount: remainder, resourceAddress, memo: toMemo(memo) }))
|
|
899
|
-
.toRevealedOutput(revealedOutAmount + dust)
|
|
900
|
-
.payFeeFromRevealed(maxFee)
|
|
901
|
-
.prepare();
|
|
902
|
-
const ownCommitment = extractOutputCommitment(spec, 0);
|
|
903
|
-
const wallet = new OotleWallet().registerKeyProvider(account, this.signer).setDefaultSigner(account);
|
|
918
|
+
.toRevealedOutput(revealedOutAmount + dust);
|
|
904
919
|
const viewSecret = await this.signer.getViewSecret();
|
|
905
|
-
// See shield()'s comment: must pass this account's real network, not fromSpec's LocalNet
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
920
|
+
// See shield()'s comment: must pass this account's real network, not fromSpec's LocalNet
|
|
921
|
+
// default. `commitments` is excluded from the fee UTXO's own selection so the same shielded
|
|
922
|
+
// record can never be spent twice in one transaction (as both a main input here and the fee
|
|
923
|
+
// input) when `resourceAddress` and the fee's resource happen to be the same currency.
|
|
924
|
+
const { transactionId, spec, privateFee } = await this.prepareSignSubmit(builder, provider, account, maxFee, feeType, { viewSecret }, commitments);
|
|
925
|
+
const ownCommitment = extractOutputCommitment(spec, 0);
|
|
909
926
|
await addPendingShield({
|
|
910
927
|
transactionId,
|
|
911
928
|
accountId,
|
|
@@ -922,6 +939,9 @@ export class OotleAccount {
|
|
|
922
939
|
for (const commitment of commitments) {
|
|
923
940
|
await markShieldedOutputSpent(accountId, commitment);
|
|
924
941
|
}
|
|
942
|
+
if (privateFee && feeType.kind === "private") {
|
|
943
|
+
await this.recordPrivateFeeSpend(accountId, feeType.feeResourceAddress, privateFee, transactionId);
|
|
944
|
+
}
|
|
925
945
|
}
|
|
926
946
|
finally {
|
|
927
947
|
await removePendingShield(transactionId);
|
|
@@ -955,7 +975,7 @@ export class OotleAccount {
|
|
|
955
975
|
* substate with `SubstateNotFound`. Each is registered by address, plus every vault its own
|
|
956
976
|
* state references (the same vault-discovery this account's own address already gets).
|
|
957
977
|
*/
|
|
958
|
-
async withdrawStealthAndExecute(resourceAddress, amount, workspaceVarName, followUpInstructions, relatedComponents = [], maxFee = 100000n) {
|
|
978
|
+
async withdrawStealthAndExecute(resourceAddress, amount, workspaceVarName, followUpInstructions, relatedComponents = [], maxFee = 100000n, feeType = { kind: "transparent" }) {
|
|
959
979
|
if (amount <= 0n)
|
|
960
980
|
throw new Error(`withdrawStealthAndExecute amount must be > 0, got ${amount}`);
|
|
961
981
|
const provider = await this.getProvider();
|
|
@@ -983,14 +1003,13 @@ export class OotleAccount {
|
|
|
983
1003
|
builder = builder.withBuilder((b) => b.addInput({ substate_id: vaultId, version: null }));
|
|
984
1004
|
}
|
|
985
1005
|
}
|
|
986
|
-
const spec = await builder.payFeeFromRevealed(maxFee).prepare();
|
|
987
|
-
const wallet = new OotleWallet().registerKeyProvider(account, this.signer).setDefaultSigner(account);
|
|
988
1006
|
const viewSecret = await this.signer.getViewSecret();
|
|
989
|
-
const
|
|
990
|
-
const envelope = await authorized.seal();
|
|
991
|
-
const transactionId = await submitTransaction(provider, envelope);
|
|
1007
|
+
const { transactionId, privateFee } = await this.prepareSignSubmit(builder, provider, account, maxFee, feeType, { viewSecret });
|
|
992
1008
|
const response = await withTimeout(pollTransactionResult(provider, transactionId), 60_000, "submitting the transaction");
|
|
993
1009
|
await recordKnownVersions(response);
|
|
1010
|
+
if (privateFee && feeType.kind === "private") {
|
|
1011
|
+
await this.recordPrivateFeeSpend(localAccountId(this.index), feeType.feeResourceAddress, privateFee, transactionId);
|
|
1012
|
+
}
|
|
994
1013
|
return { transactionId };
|
|
995
1014
|
}
|
|
996
1015
|
/**
|
|
@@ -1021,7 +1040,7 @@ export class OotleAccount {
|
|
|
1021
1040
|
* @param relatedComponents Every *other* component `followUpInstructions` touches — same
|
|
1022
1041
|
* reasoning as `withdrawStealthAndExecute`'s own parameter of the same name.
|
|
1023
1042
|
*/
|
|
1024
|
-
async redeemStealthOutputAndExecute(resourceAddress, commitmentHex, revealedAmount, followUpInstructions, relatedComponents = [], maxFee = 100000n) {
|
|
1043
|
+
async redeemStealthOutputAndExecute(resourceAddress, commitmentHex, revealedAmount, followUpInstructions, relatedComponents = [], maxFee = 100000n, feeType = { kind: "transparent" }) {
|
|
1025
1044
|
if (revealedAmount <= 0n) {
|
|
1026
1045
|
throw new Error(`redeemStealthOutputAndExecute: revealedAmount must be > 0, got ${revealedAmount}`);
|
|
1027
1046
|
}
|
|
@@ -1046,6 +1065,7 @@ export class OotleAccount {
|
|
|
1046
1065
|
agg_range_proof: "",
|
|
1047
1066
|
});
|
|
1048
1067
|
const statement = new StealthTransferStatement(new StealthInputsStatement([], 0n, inputsJson), new StealthOutputsStatement(outputsJson));
|
|
1068
|
+
const privateFee = await this.resolvePrivateFee(feeType, maxFee);
|
|
1049
1069
|
const maxEpoch = await resolveMaxEpoch(provider);
|
|
1050
1070
|
const builder = TransactionBuilder.new(this.network, maxEpoch)
|
|
1051
1071
|
.addInstruction({
|
|
@@ -1060,11 +1080,22 @@ export class OotleAccount {
|
|
|
1060
1080
|
// (name resolution only applies to instructions built through *this* same chain; raw
|
|
1061
1081
|
// pre-built instructions bypass it, same constraint `withdrawStealthAndExecute` documents).
|
|
1062
1082
|
.saveVar("redeemed")
|
|
1063
|
-
.withInstructions(followUpInstructions)
|
|
1064
|
-
|
|
1083
|
+
.withInstructions(followUpInstructions);
|
|
1084
|
+
if (privateFee) {
|
|
1085
|
+
for (const instr of privateFee.feeInstructions)
|
|
1086
|
+
builder.addFeeInstruction(instr);
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1089
|
+
builder.feeTransactionPayFromComponent(account, maxFee);
|
|
1090
|
+
}
|
|
1091
|
+
builder
|
|
1065
1092
|
.addInput({ substate_id: resourceAddress, version: null })
|
|
1066
1093
|
.addInput({ substate_id: stealthUtxoSubstateId(resourceAddress, commitment), version: null })
|
|
1067
1094
|
.addInput({ substate_id: account, version: null });
|
|
1095
|
+
if (privateFee) {
|
|
1096
|
+
for (const input of privateFee.feeInputs)
|
|
1097
|
+
builder.addInput(input);
|
|
1098
|
+
}
|
|
1068
1099
|
for (const vaultId of await getVaultIdsForAccount(provider, account)) {
|
|
1069
1100
|
builder.addInput({ substate_id: vaultId, version: null });
|
|
1070
1101
|
}
|
|
@@ -1092,10 +1123,13 @@ export class OotleAccount {
|
|
|
1092
1123
|
const wallet = new OotleWallet().registerKeyProvider(account, this.signer).setDefaultSigner(account);
|
|
1093
1124
|
const viewSecret = await this.signer.getViewSecret();
|
|
1094
1125
|
const authorized = await WalletStealthAuthorizer.fromSpec(wallet, spec, { viewSecret, crypto: new WasmStealthCrypto(this.network) }).prepare(provider);
|
|
1095
|
-
const envelope = await authorized.seal();
|
|
1126
|
+
const envelope = privateFee ? await this.sealWithPrivateFee(authorized, privateFee.feeSigner) : await authorized.seal();
|
|
1096
1127
|
const transactionId = await submitTransaction(provider, envelope);
|
|
1097
1128
|
const response = await withTimeout(pollTransactionResult(provider, transactionId), 60_000, "submitting the redemption transaction");
|
|
1098
1129
|
await recordKnownVersions(response);
|
|
1130
|
+
if (privateFee && feeType.kind === "private") {
|
|
1131
|
+
await this.recordPrivateFeeSpend(localAccountId(this.index), feeType.feeResourceAddress, privateFee, transactionId);
|
|
1132
|
+
}
|
|
1099
1133
|
return { transactionId };
|
|
1100
1134
|
}
|
|
1101
1135
|
/**
|
|
@@ -1141,49 +1175,275 @@ export class OotleAccount {
|
|
|
1141
1175
|
* (which records its own outputs locally) — callers doing several of these in sequence must
|
|
1142
1176
|
* thread this value into the next call's `feeCommitmentHex` themselves.
|
|
1143
1177
|
*/
|
|
1178
|
+
/**
|
|
1179
|
+
* Builds the fee-payment instructions/inputs for spending a single stealth UTXO of
|
|
1180
|
+
* `feeResourceAddress` to cover `maxFee`, plus the one-time `Signer` that must co-sign the
|
|
1181
|
+
* resulting `StealthTransfer` fee spend (an ordinary account signature can't authorize a
|
|
1182
|
+
* stealth spend -- see `redeemStealthOutputWithPrivateFee`'s doc comment, which this method's
|
|
1183
|
+
* fee-half logic was extracted from verbatim). The unspent remainder above `maxFee` becomes a
|
|
1184
|
+
* new stealth output back to this account (`feeChangeCommitment`); this only builds
|
|
1185
|
+
* instructions, it doesn't touch local storage -- the caller records the change output (e.g.
|
|
1186
|
+
* via `recordKnownShieldedOutput`) and marks `feeCommitmentHex` spent once the transaction
|
|
1187
|
+
* actually lands, exactly as `redeemStealthOutputWithPrivateFee` itself does inline.
|
|
1188
|
+
*
|
|
1189
|
+
* Deliberately single-UTXO only, like the method this was extracted from -- the only shape
|
|
1190
|
+
* confirmed live. Aggregating several stealth inputs into one `StealthTransfer` fee spend
|
|
1191
|
+
* isn't proven here: `unshield()`'s multi-commitment coin selection uses a different,
|
|
1192
|
+
* higher-level signing pipeline (`WalletStealthAuthorizer`) that isn't available inside this
|
|
1193
|
+
* raw `TransactionBuilder`/`resolveTransaction`/`signTransaction` pipeline. Callers needing
|
|
1194
|
+
* more than one stealth input's worth of fee should pick the smallest single covering UTXO,
|
|
1195
|
+
* not sum several.
|
|
1196
|
+
*/
|
|
1197
|
+
async buildPrivateFeeInstructions(feeResourceAddress, feeCommitmentHex, maxFee) {
|
|
1198
|
+
if (maxFee <= 0n)
|
|
1199
|
+
throw new Error(`buildPrivateFeeInstructions: maxFee must be > 0, got ${maxFee}`);
|
|
1200
|
+
const provider = await this.getProvider();
|
|
1201
|
+
const crypto = new WasmStealthCrypto(this.network);
|
|
1202
|
+
const walletAddress = await this.getWalletAddress();
|
|
1203
|
+
const viewSecret = await this.signer.getViewSecret();
|
|
1204
|
+
const commitment = fromHex(feeCommitmentHex);
|
|
1205
|
+
const substateId = stealthUtxoSubstateId(feeResourceAddress, commitment);
|
|
1206
|
+
const substate = await provider.getSubstate(substateId);
|
|
1207
|
+
const decrypted = await decryptOwnedUtxo(crypto, viewSecret, substate, substateId);
|
|
1208
|
+
if (!decrypted) {
|
|
1209
|
+
throw new Error(`buildPrivateFeeInstructions: cannot decrypt ${substateId} -- it doesn't belong to this account, or is already spent.`);
|
|
1210
|
+
}
|
|
1211
|
+
if (decrypted.value <= maxFee) {
|
|
1212
|
+
throw new Error(`buildPrivateFeeInstructions: fee UTXO (${decrypted.value}) is too small to cover maxFee (${maxFee})`);
|
|
1213
|
+
}
|
|
1214
|
+
const feeChange = decrypted.value - maxFee;
|
|
1215
|
+
const inputSkeleton = JSON.stringify({ inputs: [{ commitment: feeCommitmentHex, witness: "KeyPath" }], revealed_amount: "0" });
|
|
1216
|
+
const feeChangeOutput = createOutput({ destination: walletAddress, amount: feeChange, resourceAddress: feeResourceAddress });
|
|
1217
|
+
const { statement: feeOutputsStatement, outputMask: feeOutputMask } = await crypto.generateOutputsStatement([feeChangeOutput], maxFee);
|
|
1218
|
+
const feeInputsStatement = new StealthInputsStatement([], 0n, inputSkeleton);
|
|
1219
|
+
const feeAggInputMask = await crypto.aggregateInputMasks([decrypted.mask]);
|
|
1220
|
+
const feeBalanceProof = await crypto.generateBalanceProofSignature(feeAggInputMask, feeOutputMask, feeInputsStatement.statementJson, feeOutputsStatement.statementJson);
|
|
1221
|
+
const feeStatement = new StealthTransferStatement(feeInputsStatement, feeOutputsStatement, feeBalanceProof);
|
|
1222
|
+
await crypto.validateTransfer(feeStatement);
|
|
1223
|
+
const feeChangeCommitment = feeOutputsStatement.parsed().outputs[0].output.commitment;
|
|
1224
|
+
const feeInstructions = [
|
|
1225
|
+
{
|
|
1226
|
+
StealthTransfer: {
|
|
1227
|
+
resource_address_ref: { Address: feeResourceAddress },
|
|
1228
|
+
statement: { __ootleRawJson: feeStatement.toCompactJson() },
|
|
1229
|
+
revealed_input_bucket: null,
|
|
1230
|
+
},
|
|
1231
|
+
},
|
|
1232
|
+
{ PutLastInstructionOutputOnWorkspace: { key: 0 } },
|
|
1233
|
+
{ PayFeeFromBucket: { bucket: { id: 0, offset: null } } },
|
|
1234
|
+
];
|
|
1235
|
+
const feeInputs = [
|
|
1236
|
+
{ substate_id: feeResourceAddress, version: null },
|
|
1237
|
+
{ substate_id: substateId, version: null },
|
|
1238
|
+
];
|
|
1239
|
+
// The fee input's own sender_public_nonce lives on its UTXO substate -- fetched again here
|
|
1240
|
+
// (decryptOwnedUtxo above didn't return it) rather than parsed out of the substate response
|
|
1241
|
+
// type, whose shape `decryptOwnedUtxo` already validated once. Same convention
|
|
1242
|
+
// `redeemStealthOutputWithPrivateFee` used before this was extracted.
|
|
1243
|
+
const feeSubstate = await provider.getSubstate(substateId);
|
|
1244
|
+
const feeNonce = feeSubstate.substate.Utxo.output
|
|
1245
|
+
.output.public_nonce;
|
|
1246
|
+
const feeSigner = {
|
|
1247
|
+
getAddress: async () => walletAddress,
|
|
1248
|
+
getPublicKey: async () => parseOotleAddress(walletAddress).owner_key,
|
|
1249
|
+
signTransaction: async (tx, sealPublicKey) => {
|
|
1250
|
+
const json = serializeUnsignedTx(tx);
|
|
1251
|
+
const sig = await this.signer.addStealthSignature(json, fromHex(feeNonce), sealPublicKey, { crypto });
|
|
1252
|
+
return [sig];
|
|
1253
|
+
},
|
|
1254
|
+
};
|
|
1255
|
+
return { feeInstructions, feeInputs, feeChangeCommitment, feeChangeAmount: feeChange, feeSigner };
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Wires a `buildPrivateFeeInstructions()` result into a `StealthTransfer` builder in place of
|
|
1259
|
+
* `.payFeeFromRevealed(maxFee)` -- via the builder's `withBuilder` escape hatch, since the
|
|
1260
|
+
* class has no first-class "pay fee from a stealth UTXO" method of its own.
|
|
1261
|
+
*/
|
|
1262
|
+
attachPrivateFee(builder, fee) {
|
|
1263
|
+
return builder.withBuilder((b) => {
|
|
1264
|
+
for (const instr of fee.feeInstructions)
|
|
1265
|
+
b.addFeeInstruction(instr);
|
|
1266
|
+
for (const input of fee.feeInputs)
|
|
1267
|
+
b.addInput(input);
|
|
1268
|
+
return b;
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Completes signing for a `StealthTransfer`-based operation whose fee was attached via
|
|
1273
|
+
* `attachPrivateFee`: adds the fee UTXO's one-time authorization as an extra signature
|
|
1274
|
+
* (`AuthorizedTransfer.addSignature`) alongside whatever `WalletStealthAuthorizer` already
|
|
1275
|
+
* produced for the operation's own stealth inputs, then seals. Bound to the same seal public
|
|
1276
|
+
* key `authorized` itself uses, so every signature verifies against the same tx hash (see
|
|
1277
|
+
* `AuthorizedTransfer.seal`'s doc comment).
|
|
1278
|
+
*/
|
|
1279
|
+
async sealWithPrivateFee(authorized, feeSigner) {
|
|
1280
|
+
const sealPublicKey = authorized.getSealPublicKey();
|
|
1281
|
+
const signatures = await feeSigner.signTransaction(authorized.getSpec().unsignedTx, sealPublicKey);
|
|
1282
|
+
for (const signature of signatures)
|
|
1283
|
+
authorized.addSignature(signature);
|
|
1284
|
+
return authorized.seal();
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Shared bookkeeping after ANY operation (`execute()` included) whose fee was paid privately
|
|
1288
|
+
* lands on-chain: records the fee UTXO's change output and marks the spent one, mirroring
|
|
1289
|
+
* `shield()`/`unshield()`'s existing pattern for their own outputs. Never call this for a dry
|
|
1290
|
+
* run or a failed submission.
|
|
1291
|
+
*/
|
|
1292
|
+
async recordPrivateFeeSpend(accountId, feeResourceAddress, fee, transactionId) {
|
|
1293
|
+
await recordKnownShieldedOutput(accountId, feeResourceAddress, fee.feeChangeCommitment, fee.feeChangeAmount, transactionId);
|
|
1294
|
+
await markShieldedOutputSpent(accountId, fee.spentCommitment);
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Selects and builds everything needed to pay a transaction's fee privately, or returns `null`
|
|
1298
|
+
* for `{ kind: "transparent" }` -- the single entry point every fee-paying method (`execute()`
|
|
1299
|
+
* and every `StealthTransfer`-based one below) uses to interpret a `FeeType` the same way.
|
|
1300
|
+
*/
|
|
1301
|
+
async resolvePrivateFee(feeType, maxFee,
|
|
1302
|
+
/** Commitments already claimed by the operation's own main effect (e.g. `unshield()`'s or
|
|
1303
|
+
* `sendPrivately()`'s own multi-UTXO coin selection) -- excluded so the same commitment can
|
|
1304
|
+
* never be selected as both a main input and the fee input in one transaction. */
|
|
1305
|
+
exclude = []) {
|
|
1306
|
+
if (feeType.kind !== "private")
|
|
1307
|
+
return null;
|
|
1308
|
+
const accountId = localAccountId(this.index);
|
|
1309
|
+
const records = await listShieldedOutputs(accountId);
|
|
1310
|
+
// Retries with the next-smallest qualifying UTXO if one fails to read -- confirmed live that a
|
|
1311
|
+
// record this wallet still has as "unspent" can 500 on the indexer (consistent with it having
|
|
1312
|
+
// actually been spent already, e.g. from another device/session sharing this seed, hitting an
|
|
1313
|
+
// indexer bug serving a since-spent substate) rather than a clean not-found. Deliberately does
|
|
1314
|
+
// NOT mark a failing candidate spent here: a 500 doesn't distinguish "genuinely spent" from "the
|
|
1315
|
+
// indexer had a bad moment," and this wallet's only handle back to a real stealth output is this
|
|
1316
|
+
// local record (see `ShieldedOutputRecord`'s own doc comment) -- wrongly deleting one on an
|
|
1317
|
+
// ambiguous error is unrecoverable, so a failing candidate is only skipped for *this* attempt,
|
|
1318
|
+
// tried again next time. Bounded by the exclude set strictly growing each iteration.
|
|
1319
|
+
const tried = new Set(exclude);
|
|
1320
|
+
let lastReadError;
|
|
1321
|
+
for (;;) {
|
|
1322
|
+
let chosen;
|
|
1323
|
+
try {
|
|
1324
|
+
chosen = selectPrivateFeeUtxo(records, feeType.feeResourceAddress, maxFee, [...tried]);
|
|
1325
|
+
}
|
|
1326
|
+
catch (selectError) {
|
|
1327
|
+
// Distinguishes "never had a large-enough candidate" (selectPrivateFeeUtxo's own clear
|
|
1328
|
+
// error) from "had one or more, but every single one failed to read" -- the latter needs
|
|
1329
|
+
// its own message naming the last read failure, or it looks identical to simply not having
|
|
1330
|
+
// enough shielded balance.
|
|
1331
|
+
if (lastReadError) {
|
|
1332
|
+
throw new Error(`Every shielded ${feeType.feeResourceAddress} UTXO large enough to cover a private fee of ${maxFee} failed to read from the network (last error: ${lastReadError instanceof Error ? lastReadError.message : String(lastReadError)}) -- one or more may already be spent, e.g. from another device or session sharing this seed.`, { cause: lastReadError });
|
|
1333
|
+
}
|
|
1334
|
+
throw selectError;
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
const built = await this.buildPrivateFeeInstructions(feeType.feeResourceAddress, chosen.commitment, maxFee);
|
|
1338
|
+
return { ...built, spentCommitment: chosen.commitment };
|
|
1339
|
+
}
|
|
1340
|
+
catch (e) {
|
|
1341
|
+
lastReadError = e;
|
|
1342
|
+
tried.add(chosen.commitment);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Filters out unspent local records for `resourceAddress` that can't actually be read right now
|
|
1348
|
+
* (checked largest-first, stopping once verified-readable records cover `targetAmount` -- the
|
|
1349
|
+
* same greedy order `selectShieldedUtxosForAmount` itself uses, so this does the minimum
|
|
1350
|
+
* verification needed rather than checking every record up front) -- used by `sendPrivately()`
|
|
1351
|
+
* and `unshield()` before their own coin-selection plan runs, so a record that's actually
|
|
1352
|
+
* already spent (confirmed live: the indexer can 500 instead of cleanly saying so, e.g. for one
|
|
1353
|
+
* spent through another device/session sharing this seed) gets skipped as a *candidate* instead
|
|
1354
|
+
* of failing the whole transaction deep inside the `StealthTransfer` builder with no way to
|
|
1355
|
+
* retry excluding just the bad one.
|
|
1356
|
+
*
|
|
1357
|
+
* Deliberately never marks a failing record spent in local storage -- same reasoning as
|
|
1358
|
+
* `resolvePrivateFee`: an unreadable-right-now record could just as easily be a transient
|
|
1359
|
+
* indexer error as a genuinely spent output, and this wallet's only handle back to a real
|
|
1360
|
+
* stealth output is this local record (see `ShieldedOutputRecord`'s own doc comment), so wrongly
|
|
1361
|
+
* deleting one is unrecoverable. A record skipped here is simply not offered as a candidate for
|
|
1362
|
+
* *this* attempt; it stays in local storage to be tried again later.
|
|
1363
|
+
*/
|
|
1364
|
+
async filterReadableShieldedOutputs(records, resourceAddress, targetAmount) {
|
|
1365
|
+
const provider = await this.getProvider();
|
|
1366
|
+
const crypto = new WasmStealthCrypto(this.network);
|
|
1367
|
+
const viewSecret = await this.signer.getViewSecret();
|
|
1368
|
+
const candidates = records
|
|
1369
|
+
.filter((r) => r.resourceAddress === resourceAddress && !r.spent)
|
|
1370
|
+
.sort((a, b) => {
|
|
1371
|
+
const diff = BigInt(b.amount) - BigInt(a.amount);
|
|
1372
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
1373
|
+
});
|
|
1374
|
+
const unreadable = new Set();
|
|
1375
|
+
let verifiedTotal = 0n;
|
|
1376
|
+
for (const record of candidates) {
|
|
1377
|
+
if (verifiedTotal >= targetAmount)
|
|
1378
|
+
break;
|
|
1379
|
+
try {
|
|
1380
|
+
const substateId = stealthUtxoSubstateId(resourceAddress, fromHex(record.commitment));
|
|
1381
|
+
const substate = await provider.getSubstate(substateId);
|
|
1382
|
+
const decrypted = await decryptOwnedUtxo(crypto, viewSecret, substate, substateId);
|
|
1383
|
+
if (!decrypted) {
|
|
1384
|
+
unreadable.add(record.commitment); // doesn't decrypt as ours -- don't offer it either
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
verifiedTotal += BigInt(record.amount);
|
|
1388
|
+
}
|
|
1389
|
+
catch {
|
|
1390
|
+
unreadable.add(record.commitment);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return records.filter((r) => !unreadable.has(r.commitment));
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Shared prepare/authorize/seal/submit path for every `StealthTransfer`-based operation
|
|
1397
|
+
* (shield/unshield/withdrawStealthAndExecute/etc.): `builder` must already have every
|
|
1398
|
+
* operation-specific call applied (`spendRevealedInput`, `toStealthOutput`,
|
|
1399
|
+
* `spendStealthInput`, `andThen`, ...) EXCEPT the fee -- this attaches either
|
|
1400
|
+
* `.payFeeFromRevealed(maxFee)` or a private fee (via `resolvePrivateFee`/`attachPrivateFee`),
|
|
1401
|
+
* then authorizes with `WalletStealthAuthorizer` (passing `authorizerOpts` through, e.g.
|
|
1402
|
+
* `viewSecret` when the operation itself spends stealth inputs), seals (co-signing the private
|
|
1403
|
+
* fee's UTXO when there is one), and submits -- but does NOT poll to finality or record
|
|
1404
|
+
* anything: callers keep their own existing `addPendingShield`/poll/`recordKnownShieldedOutput`
|
|
1405
|
+
* logic for the operation's own effect, and must call `recordPrivateFeeSpend` themselves once
|
|
1406
|
+
* `transactionId` actually lands (never for a dry run) using the returned `privateFee`.
|
|
1407
|
+
*/
|
|
1408
|
+
async prepareSignSubmit(builder, provider, account, maxFee, feeType, authorizerOpts = {},
|
|
1409
|
+
/** See `resolvePrivateFee`'s own doc comment. */
|
|
1410
|
+
excludeFromFeeSelection = []) {
|
|
1411
|
+
const privateFee = await this.resolvePrivateFee(feeType, maxFee, excludeFromFeeSelection);
|
|
1412
|
+
const spec = await (privateFee ? this.attachPrivateFee(builder, privateFee) : builder.payFeeFromRevealed(maxFee)).prepare();
|
|
1413
|
+
const wallet = new OotleWallet().registerKeyProvider(account, this.signer).setDefaultSigner(account);
|
|
1414
|
+
const authorized = await WalletStealthAuthorizer.fromSpec(wallet, spec, {
|
|
1415
|
+
crypto: new WasmStealthCrypto(this.network),
|
|
1416
|
+
...authorizerOpts,
|
|
1417
|
+
}).prepare(provider);
|
|
1418
|
+
const envelope = privateFee ? await this.sealWithPrivateFee(authorized, privateFee.feeSigner) : await authorized.seal();
|
|
1419
|
+
const transactionId = await submitTransaction(provider, envelope);
|
|
1420
|
+
return { transactionId, spec, privateFee };
|
|
1421
|
+
}
|
|
1144
1422
|
async redeemStealthOutputWithPrivateFee(resourceAddress, commitmentHex, revealedAmount, followUpInstructions, feeResourceAddress, feeCommitmentHex, maxFee, relatedComponents = []) {
|
|
1145
1423
|
if (revealedAmount <= 0n)
|
|
1146
1424
|
throw new Error(`redeemStealthOutputWithPrivateFee: revealedAmount must be > 0, got ${revealedAmount}`);
|
|
1147
|
-
if (maxFee <= 0n)
|
|
1148
|
-
throw new Error(`redeemStealthOutputWithPrivateFee: maxFee must be > 0, got ${maxFee}`);
|
|
1149
1425
|
const provider = await this.getProvider();
|
|
1150
1426
|
const crypto = new WasmStealthCrypto(this.network);
|
|
1151
1427
|
const walletAddress = await this.getWalletAddress();
|
|
1152
1428
|
const viewSecret = await this.signer.getViewSecret();
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
throw new Error(`redeemStealthOutputWithPrivateFee: cannot decrypt ${substateId} -- it doesn't belong to this account, or is already spent.`);
|
|
1160
|
-
}
|
|
1161
|
-
return { commitmentHex: commitmentHexIn, substateId, mask: decrypted.mask, value: decrypted.value };
|
|
1162
|
-
};
|
|
1163
|
-
const redeemed = await decryptStealthInput(resourceAddress, commitmentHex);
|
|
1164
|
-
const feeInput = await decryptStealthInput(feeResourceAddress, feeCommitmentHex);
|
|
1165
|
-
if (feeInput.value <= maxFee) {
|
|
1166
|
-
throw new Error(`redeemStealthOutputWithPrivateFee: fee UTXO (${feeInput.value}) is too small to cover maxFee (${maxFee})`);
|
|
1429
|
+
const commitment = fromHex(commitmentHex);
|
|
1430
|
+
const substateId = stealthUtxoSubstateId(resourceAddress, commitment);
|
|
1431
|
+
const substate = await provider.getSubstate(substateId);
|
|
1432
|
+
const decrypted = await decryptOwnedUtxo(crypto, viewSecret, substate, substateId);
|
|
1433
|
+
if (!decrypted) {
|
|
1434
|
+
throw new Error(`redeemStealthOutputWithPrivateFee: cannot decrypt ${substateId} -- it doesn't belong to this account, or is already spent.`);
|
|
1167
1435
|
}
|
|
1168
|
-
const
|
|
1169
|
-
const
|
|
1436
|
+
const redeemed = { commitmentHex, substateId, mask: decrypted.mask, value: decrypted.value };
|
|
1437
|
+
const { feeInstructions, feeInputs, feeChangeCommitment, feeSigner } = await this.buildPrivateFeeInstructions(feeResourceAddress, feeCommitmentHex, maxFee);
|
|
1438
|
+
const inputSkeleton = JSON.stringify({ inputs: [{ commitment: redeemed.commitmentHex, witness: "KeyPath" }], revealed_amount: "0" });
|
|
1170
1439
|
// Redeemed resource: 1 stealth input -> 0 stealth outputs, fully revealed into the bucket
|
|
1171
1440
|
// `followUpInstructions` consumes.
|
|
1172
|
-
const redeemedInputsStatement = new StealthInputsStatement([], 0n, inputSkeleton
|
|
1441
|
+
const redeemedInputsStatement = new StealthInputsStatement([], 0n, inputSkeleton);
|
|
1173
1442
|
const redeemedOutputsStatement = new StealthOutputsStatement(JSON.stringify({ outputs: [], revealed_output_amount: revealedAmount.toString(), agg_range_proof: "" }));
|
|
1174
1443
|
const redeemedAggInputMask = await crypto.aggregateInputMasks([redeemed.mask]);
|
|
1175
1444
|
const redeemedBalanceProof = await crypto.generateBalanceProofSignature(redeemedAggInputMask, Mask.zero(), redeemedInputsStatement.statementJson, redeemedOutputsStatement.statementJson);
|
|
1176
1445
|
const redeemedStatement = new StealthTransferStatement(redeemedInputsStatement, redeemedOutputsStatement, redeemedBalanceProof);
|
|
1177
1446
|
await crypto.validateTransfer(redeemedStatement);
|
|
1178
|
-
// Fee resource: 1 stealth input -> 1 stealth change output + revealed maxFee.
|
|
1179
|
-
const feeChangeOutput = createOutput({ destination: walletAddress, amount: feeChange, resourceAddress: feeResourceAddress });
|
|
1180
|
-
const { statement: feeOutputsStatement, outputMask: feeOutputMask } = await crypto.generateOutputsStatement([feeChangeOutput], maxFee);
|
|
1181
|
-
const feeInputsStatement = new StealthInputsStatement([], 0n, inputSkeleton(feeInput.commitmentHex));
|
|
1182
|
-
const feeAggInputMask = await crypto.aggregateInputMasks([feeInput.mask]);
|
|
1183
|
-
const feeBalanceProof = await crypto.generateBalanceProofSignature(feeAggInputMask, feeOutputMask, feeInputsStatement.statementJson, feeOutputsStatement.statementJson);
|
|
1184
|
-
const feeStatement = new StealthTransferStatement(feeInputsStatement, feeOutputsStatement, feeBalanceProof);
|
|
1185
|
-
await crypto.validateTransfer(feeStatement);
|
|
1186
|
-
const feeChangeCommitment = feeOutputsStatement.parsed().outputs[0].output.commitment;
|
|
1187
1447
|
const maxEpoch = await resolveMaxEpoch(provider);
|
|
1188
1448
|
const builder = TransactionBuilder.new(this.network, maxEpoch)
|
|
1189
1449
|
.addInstruction({
|
|
@@ -1194,20 +1454,12 @@ export class OotleAccount {
|
|
|
1194
1454
|
},
|
|
1195
1455
|
})
|
|
1196
1456
|
.addInstruction({ PutLastInstructionOutputOnWorkspace: { key: 0 } })
|
|
1197
|
-
.withInstructions(followUpInstructions)
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
},
|
|
1204
|
-
})
|
|
1205
|
-
.addFeeInstruction({ PutLastInstructionOutputOnWorkspace: { key: 0 } })
|
|
1206
|
-
.addFeeInstruction({ PayFeeFromBucket: { bucket: { id: 0, offset: null } } })
|
|
1207
|
-
.addInput({ substate_id: resourceAddress, version: null })
|
|
1208
|
-
.addInput({ substate_id: redeemed.substateId, version: null })
|
|
1209
|
-
.addInput({ substate_id: feeResourceAddress, version: null })
|
|
1210
|
-
.addInput({ substate_id: feeInput.substateId, version: null });
|
|
1457
|
+
.withInstructions(followUpInstructions);
|
|
1458
|
+
for (const instr of feeInstructions)
|
|
1459
|
+
builder.addFeeInstruction(instr);
|
|
1460
|
+
builder.addInput({ substate_id: resourceAddress, version: null }).addInput({ substate_id: redeemed.substateId, version: null });
|
|
1461
|
+
for (const input of feeInputs)
|
|
1462
|
+
builder.addInput(input);
|
|
1211
1463
|
for (const component of relatedComponents) {
|
|
1212
1464
|
builder.addInput({ substate_id: component, version: null });
|
|
1213
1465
|
for (const vaultId of await getVaultIdsForAccount(provider, component)) {
|
|
@@ -1215,28 +1467,22 @@ export class OotleAccount {
|
|
|
1215
1467
|
}
|
|
1216
1468
|
}
|
|
1217
1469
|
const unsignedTx = await resolveTransaction(provider, builder.buildUnsignedTransaction());
|
|
1218
|
-
//
|
|
1219
|
-
//
|
|
1470
|
+
// The redeemed input's one-time key co-signs the same way the fee input's does inside
|
|
1471
|
+
// `buildPrivateFeeInstructions` -- see this method's own doc comment for why no ordinary
|
|
1220
1472
|
// account-key signature is added.
|
|
1221
|
-
const
|
|
1473
|
+
const redeemedSubstate = await provider.getSubstate(redeemed.substateId);
|
|
1474
|
+
const redeemedNonce = redeemedSubstate.substate.Utxo
|
|
1475
|
+
.output.output.public_nonce;
|
|
1476
|
+
const redeemedSigner = {
|
|
1222
1477
|
getAddress: async () => walletAddress,
|
|
1223
1478
|
getPublicKey: async () => parseOotleAddress(walletAddress).owner_key,
|
|
1224
1479
|
signTransaction: async (tx, sealPublicKey) => {
|
|
1225
1480
|
const json = serializeUnsignedTx(tx);
|
|
1226
|
-
const sig = await this.signer.addStealthSignature(json, fromHex(
|
|
1481
|
+
const sig = await this.signer.addStealthSignature(json, fromHex(redeemedNonce), sealPublicKey, { crypto });
|
|
1227
1482
|
return [sig];
|
|
1228
1483
|
},
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// here (decryptOwnedUtxo above didn't return it) rather than parsed out of the substate
|
|
1232
|
-
// response type, whose shape `decryptOwnedUtxo` already validated once.
|
|
1233
|
-
const redeemedSubstate = await provider.getSubstate(redeemed.substateId);
|
|
1234
|
-
const feeInputSubstate = await provider.getSubstate(feeInput.substateId);
|
|
1235
|
-
const redeemedNonce = redeemedSubstate.substate.Utxo
|
|
1236
|
-
.output.output.public_nonce;
|
|
1237
|
-
const feeNonce = feeInputSubstate.substate.Utxo.output
|
|
1238
|
-
.output.public_nonce;
|
|
1239
|
-
const signed = await signTransaction([oneTimeSigner(redeemedNonce), oneTimeSigner(feeNonce)], unsignedTx);
|
|
1484
|
+
};
|
|
1485
|
+
const signed = await signTransaction([redeemedSigner, feeSigner], unsignedTx);
|
|
1240
1486
|
const envelope = sealTransaction(signed);
|
|
1241
1487
|
const transactionId = await submitTransaction(provider, envelope);
|
|
1242
1488
|
const response = await withTimeout(pollTransactionResult(provider, transactionId), 60_000, "submitting the private-fee redemption transaction");
|
|
@@ -1273,7 +1519,7 @@ export class OotleAccount {
|
|
|
1273
1519
|
* other stealth payment addressed to them — spend authority is governed entirely separately, by
|
|
1274
1520
|
* `payTo`.
|
|
1275
1521
|
*/
|
|
1276
|
-
async htlcFund(resourceAddress, amount, claimantWalletAddress, hashLockHex, refundEpoch, maxFee = 50000n) {
|
|
1522
|
+
async htlcFund(resourceAddress, amount, claimantWalletAddress, hashLockHex, refundEpoch, maxFee = 50000n, feeType = { kind: "transparent" }) {
|
|
1277
1523
|
const provider = await this.getProvider();
|
|
1278
1524
|
const account = await this.getComponentAddress();
|
|
1279
1525
|
const walletAddress = await this.getWalletAddress();
|
|
@@ -1287,21 +1533,20 @@ export class OotleAccount {
|
|
|
1287
1533
|
});
|
|
1288
1534
|
// See shield()'s comment: the resource's own substate must be pinned explicitly -- prepare()
|
|
1289
1535
|
// never adds it on its own.
|
|
1290
|
-
const
|
|
1536
|
+
const builder = new StealthTransfer(provider, resourceAddress)
|
|
1291
1537
|
.withBuilder((b) => b.addInput({ substate_id: resourceAddress, version: null }))
|
|
1292
1538
|
.spendRevealedInput(account, amount)
|
|
1293
|
-
.toStealthOutput(createOutput({ destination: claimantWalletAddress, amount, resourceAddress, payTo: { Conditions: conditions } }))
|
|
1294
|
-
|
|
1295
|
-
|
|
1539
|
+
.toStealthOutput(createOutput({ destination: claimantWalletAddress, amount, resourceAddress, payTo: { Conditions: conditions } }));
|
|
1540
|
+
// No stealth inputs to unblind for this fund's own transfer (revealed-only source), so no
|
|
1541
|
+
// viewSecret needed here even when the FEE is paid privately -- same as shield().
|
|
1542
|
+
const { transactionId, spec, privateFee } = await this.prepareSignSubmit(builder, provider, account, maxFee, feeType);
|
|
1296
1543
|
const ownCommitment = extractOutputCommitment(spec, 0);
|
|
1297
1544
|
const outputMask = spec.outputMask.toHex();
|
|
1298
|
-
const wallet = new OotleWallet().registerKeyProvider(account, this.signer).setDefaultSigner(account);
|
|
1299
|
-
// No stealth inputs to unblind (revealed-only source), so no viewSecret needed -- same as shield().
|
|
1300
|
-
const authorized = await WalletStealthAuthorizer.fromSpec(wallet, spec, { crypto: new WasmStealthCrypto(this.network) }).prepare(provider);
|
|
1301
|
-
const envelope = await authorized.seal();
|
|
1302
|
-
const transactionId = await submitTransaction(provider, envelope);
|
|
1303
1545
|
const response = await withTimeout(pollTransactionResult(provider, transactionId), 60_000, "submitting the HTLC funding transaction");
|
|
1304
1546
|
await recordKnownVersions(response);
|
|
1547
|
+
if (privateFee && feeType.kind === "private") {
|
|
1548
|
+
await this.recordPrivateFeeSpend(localAccountId(this.index), feeType.feeResourceAddress, privateFee, transactionId);
|
|
1549
|
+
}
|
|
1305
1550
|
return { transactionId, conditions, ownCommitment, outputMask };
|
|
1306
1551
|
}
|
|
1307
1552
|
/**
|
|
@@ -1331,7 +1576,7 @@ export class OotleAccount {
|
|
|
1331
1576
|
* unblinded mask+value and the two witnesses below, so there is nothing left for the builder
|
|
1332
1577
|
* pipeline to add.
|
|
1333
1578
|
*/
|
|
1334
|
-
async htlcClaim(resourceAddress, commitmentHex, conditions, preimageHex, maxFee = 50000n) {
|
|
1579
|
+
async htlcClaim(resourceAddress, commitmentHex, conditions, preimageHex, maxFee = 50000n, feeType = { kind: "transparent" }) {
|
|
1335
1580
|
if (!/^[0-9a-f]{64}$/i.test(preimageHex)) {
|
|
1336
1581
|
throw new Error(`htlcClaim: preimageHex must be exactly 64 hex characters (32 bytes), got ${JSON.stringify(preimageHex)}`);
|
|
1337
1582
|
}
|
|
@@ -1359,6 +1604,7 @@ export class OotleAccount {
|
|
|
1359
1604
|
destinationWalletAddress: walletAddress,
|
|
1360
1605
|
resourceAddress,
|
|
1361
1606
|
});
|
|
1607
|
+
const privateFee = await this.resolvePrivateFee(feeType, maxFee);
|
|
1362
1608
|
const transactionId = await submitHtlcSpend({
|
|
1363
1609
|
provider,
|
|
1364
1610
|
signer: this.signer,
|
|
@@ -1369,7 +1615,11 @@ export class OotleAccount {
|
|
|
1369
1615
|
statementJson,
|
|
1370
1616
|
maxFee,
|
|
1371
1617
|
timeoutLabel: "submitting the HTLC claim transaction",
|
|
1618
|
+
privateFee,
|
|
1372
1619
|
});
|
|
1620
|
+
if (privateFee && feeType.kind === "private") {
|
|
1621
|
+
await this.recordPrivateFeeSpend(localAccountId(this.index), feeType.feeResourceAddress, privateFee, transactionId);
|
|
1622
|
+
}
|
|
1373
1623
|
return { transactionId };
|
|
1374
1624
|
}
|
|
1375
1625
|
/**
|
|
@@ -1390,7 +1640,7 @@ export class OotleAccount {
|
|
|
1390
1640
|
* `buildStealthTransferStatement`-in-one-call approach, same bypass of
|
|
1391
1641
|
* `StealthTransfer`/`WalletStealthAuthorizer`.
|
|
1392
1642
|
*/
|
|
1393
|
-
async htlcRefund(resourceAddress, commitmentHex, conditions, amount, outputMaskHex, maxFee = 50000n) {
|
|
1643
|
+
async htlcRefund(resourceAddress, commitmentHex, conditions, amount, outputMaskHex, maxFee = 50000n, feeType = { kind: "transparent" }) {
|
|
1394
1644
|
const provider = await this.getProvider();
|
|
1395
1645
|
const account = await this.getComponentAddress();
|
|
1396
1646
|
const walletAddress = await this.getWalletAddress();
|
|
@@ -1409,6 +1659,7 @@ export class OotleAccount {
|
|
|
1409
1659
|
destinationWalletAddress: walletAddress,
|
|
1410
1660
|
resourceAddress,
|
|
1411
1661
|
});
|
|
1662
|
+
const privateFee = await this.resolvePrivateFee(feeType, maxFee);
|
|
1412
1663
|
const transactionId = await submitHtlcSpend({
|
|
1413
1664
|
provider,
|
|
1414
1665
|
signer: this.signer,
|
|
@@ -1419,7 +1670,11 @@ export class OotleAccount {
|
|
|
1419
1670
|
statementJson,
|
|
1420
1671
|
maxFee,
|
|
1421
1672
|
timeoutLabel: "submitting the HTLC refund transaction",
|
|
1673
|
+
privateFee,
|
|
1422
1674
|
});
|
|
1675
|
+
if (privateFee && feeType.kind === "private") {
|
|
1676
|
+
await this.recordPrivateFeeSpend(localAccountId(this.index), feeType.feeResourceAddress, privateFee, transactionId);
|
|
1677
|
+
}
|
|
1423
1678
|
return { transactionId };
|
|
1424
1679
|
}
|
|
1425
1680
|
/**
|
|
@@ -1449,10 +1704,10 @@ export class OotleAccount {
|
|
|
1449
1704
|
* grabbing "the first new stealth output" would risk recording the recipient's payment as our
|
|
1450
1705
|
* own. See `PendingShield.ownCommitment`'s doc comment.
|
|
1451
1706
|
*/
|
|
1452
|
-
async sendPrivately(resourceAddress, recipientWalletAddress, amount, maxFee = 100000n, memo, minimumValuePromise = 0n) {
|
|
1707
|
+
async sendPrivately(resourceAddress, recipientWalletAddress, amount, maxFee = 100000n, memo, minimumValuePromise = 0n, feeType = { kind: "transparent" }) {
|
|
1453
1708
|
assertValidMinimumValuePromise(minimumValuePromise, amount);
|
|
1454
1709
|
const accountId = localAccountId(this.index);
|
|
1455
|
-
const records = await listShieldedOutputs(accountId);
|
|
1710
|
+
const records = await this.filterReadableShieldedOutputs(await listShieldedOutputs(accountId), resourceAddress, amount);
|
|
1456
1711
|
const { commitments, changeAmount } = resolveSendPrivatelyPlan(records, resourceAddress, amount);
|
|
1457
1712
|
const dust = 1n;
|
|
1458
1713
|
const provider = await this.getProvider();
|
|
@@ -1473,18 +1728,18 @@ export class OotleAccount {
|
|
|
1473
1728
|
if (changeAmount > 0n) {
|
|
1474
1729
|
builder = builder.toStealthOutput(createOutput({ destination: ownWalletAddress, amount: changeAmount, resourceAddress }));
|
|
1475
1730
|
}
|
|
1476
|
-
|
|
1731
|
+
builder = builder.toRevealedOutput(dust);
|
|
1732
|
+
const viewSecret = await this.signer.getViewSecret();
|
|
1733
|
+
// `commitments` excluded from the fee UTXO's own selection -- see unshield()'s identical
|
|
1734
|
+
// comment on why (this and unshield are the only two methods with their own multi-UTXO
|
|
1735
|
+
// coin selection of potentially the same resource as the fee).
|
|
1736
|
+
const { transactionId, spec, privateFee } = await this.prepareSignSubmit(builder, provider, account, maxFee, feeType, { viewSecret }, commitments);
|
|
1477
1737
|
// Output 0 is the recipient's; output 1 (only present when there's change) is ours. The
|
|
1478
1738
|
// recipient has no way to discover their new output on their own (no scan-by-view-key API
|
|
1479
1739
|
// exists) -- this commitment must be handed back to the caller so it can be shared with them
|
|
1480
1740
|
// out of band; without it, the payment is invisible to them even though it succeeded on-chain.
|
|
1481
1741
|
const recipientCommitment = extractOutputCommitment(spec, 0);
|
|
1482
1742
|
const ownCommitment = changeAmount > 0n ? extractOutputCommitment(spec, 1) : undefined;
|
|
1483
|
-
const wallet = new OotleWallet().registerKeyProvider(account, this.signer).setDefaultSigner(account);
|
|
1484
|
-
const viewSecret = await this.signer.getViewSecret();
|
|
1485
|
-
const authorized = await WalletStealthAuthorizer.fromSpec(wallet, spec, { viewSecret, crypto: new WasmStealthCrypto(this.network) }).prepare(provider);
|
|
1486
|
-
const envelope = await authorized.seal();
|
|
1487
|
-
const transactionId = await submitTransaction(provider, envelope);
|
|
1488
1743
|
await addPendingShield({
|
|
1489
1744
|
transactionId,
|
|
1490
1745
|
accountId,
|
|
@@ -1501,6 +1756,9 @@ export class OotleAccount {
|
|
|
1501
1756
|
for (const commitment of commitments) {
|
|
1502
1757
|
await markShieldedOutputSpent(accountId, commitment);
|
|
1503
1758
|
}
|
|
1759
|
+
if (privateFee && feeType.kind === "private") {
|
|
1760
|
+
await this.recordPrivateFeeSpend(accountId, feeType.feeResourceAddress, privateFee, transactionId);
|
|
1761
|
+
}
|
|
1504
1762
|
}
|
|
1505
1763
|
finally {
|
|
1506
1764
|
await removePendingShield(transactionId);
|
|
@@ -1827,7 +2085,6 @@ async function submitHtlcSpend(params) {
|
|
|
1827
2085
|
const builder = TransactionBuilder.new(params.network, maxEpoch)
|
|
1828
2086
|
.addInput({ substate_id: params.substateId, version: null })
|
|
1829
2087
|
.addInput({ substate_id: params.account, version: null })
|
|
1830
|
-
.feeTransactionPayFromComponent(params.account, params.maxFee)
|
|
1831
2088
|
.addInstruction(
|
|
1832
2089
|
// `statement` is spliced as a raw JSON fragment (see buildHtlcSpendStatement), the same
|
|
1833
2090
|
// technique `@tari-project/ootle`'s own `statementAsWire` uses internally for the piecemeal
|
|
@@ -1839,11 +2096,21 @@ async function submitHtlcSpend(params) {
|
|
|
1839
2096
|
revealed_input_bucket: null,
|
|
1840
2097
|
},
|
|
1841
2098
|
});
|
|
2099
|
+
if (params.privateFee) {
|
|
2100
|
+
for (const instr of params.privateFee.feeInstructions)
|
|
2101
|
+
builder.addFeeInstruction(instr);
|
|
2102
|
+
for (const input of params.privateFee.feeInputs)
|
|
2103
|
+
builder.addInput(input);
|
|
2104
|
+
}
|
|
2105
|
+
else {
|
|
2106
|
+
builder.feeTransactionPayFromComponent(params.account, params.maxFee);
|
|
2107
|
+
}
|
|
1842
2108
|
for (const vaultId of await getVaultIdsForAccount(params.provider, params.account)) {
|
|
1843
2109
|
builder.addInput({ substate_id: vaultId, version: null });
|
|
1844
2110
|
}
|
|
1845
2111
|
const unsignedTx = await resolveTransaction(params.provider, builder.buildUnsignedTransaction());
|
|
1846
|
-
const
|
|
2112
|
+
const extraSigners = params.privateFee ? [params.privateFee.feeSigner] : [];
|
|
2113
|
+
const signed = await signTransaction([params.signer, ...extraSigners], unsignedTx);
|
|
1847
2114
|
const envelope = await sealTransaction(signed);
|
|
1848
2115
|
const transactionId = await submitTransaction(params.provider, envelope);
|
|
1849
2116
|
const response = await withTimeout(pollTransactionResult(params.provider, transactionId), 60_000, params.timeoutLabel);
|
|
@@ -2002,6 +2269,27 @@ export function resolveUnshieldPlan(records, resourceAddress, revealedOutAmount)
|
|
|
2002
2269
|
}
|
|
2003
2270
|
return { commitments: finalSelected.map((r) => r.commitment), remainder: finalTotal - revealedOutAmount };
|
|
2004
2271
|
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Pure planning for `execute()`'s private-fee path: picks the smallest unspent stealth UTXO of
|
|
2274
|
+
* `feeResourceAddress` that alone covers `maxFee`. Deliberately the *smallest* qualifying record
|
|
2275
|
+
* (not the largest, and never several combined) -- `buildPrivateFeeInstructions` only supports a
|
|
2276
|
+
* single fee input (see its own doc comment for why), so this leaves larger shielded balances
|
|
2277
|
+
* untouched for whatever they were shielded for rather than reaching for them first.
|
|
2278
|
+
*/
|
|
2279
|
+
export function selectPrivateFeeUtxo(records, feeResourceAddress, maxFee, exclude = []) {
|
|
2280
|
+
const excluded = new Set(exclude);
|
|
2281
|
+
const candidates = records
|
|
2282
|
+
.filter((r) => !r.spent && !excluded.has(r.commitment) && r.resourceAddress === feeResourceAddress && BigInt(r.amount) > maxFee)
|
|
2283
|
+
.sort((a, b) => {
|
|
2284
|
+
const diff = BigInt(a.amount) - BigInt(b.amount);
|
|
2285
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
2286
|
+
});
|
|
2287
|
+
const chosen = candidates[0];
|
|
2288
|
+
if (!chosen) {
|
|
2289
|
+
throw new Error(`No single shielded ${feeResourceAddress} UTXO is large enough to cover a private fee of ${maxFee}. Shield some first (see shield()).`);
|
|
2290
|
+
}
|
|
2291
|
+
return chosen;
|
|
2292
|
+
}
|
|
2005
2293
|
/**
|
|
2006
2294
|
* Pure planning for `sendPrivately()`: selects enough of the caller's unspent records for this
|
|
2007
2295
|
* resource to cover `amount` — unlike `resolveUnshieldPlan`, sending the *entire* selected total
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chironbuilder/ootle-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Shared TypeScript client for Tari Ootle (L2): account derivation, balances, transactions, HTLCs, and stealth/confidential transfers — the account core behind Sapient and the Tari L1 web wallet's Ootle support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
"url": "git+https://github.com/chironbuilds/ootle-sdk-ts.git"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://github.com/chironbuilds/ootle-sdk-ts",
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"tari",
|
|
14
|
+
"ootle",
|
|
15
|
+
"wallet",
|
|
16
|
+
"blockchain",
|
|
17
|
+
"web3"
|
|
18
|
+
],
|
|
13
19
|
"main": "./dist/index.js",
|
|
14
20
|
"types": "./dist/index.d.ts",
|
|
15
21
|
"exports": {
|
|
@@ -22,7 +28,10 @@
|
|
|
22
28
|
"default": "./dist/adapters.js"
|
|
23
29
|
}
|
|
24
30
|
},
|
|
25
|
-
"files": [
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"vendor"
|
|
34
|
+
],
|
|
26
35
|
"scripts": {
|
|
27
36
|
"build": "tsc -p tsconfig.build.json",
|
|
28
37
|
"typecheck": "tsc --noEmit",
|