@zerc20/wallet-sdk 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,8 +26,9 @@ export interface Relayed {
26
26
  noteIndex1: number | null;
27
27
  noteIndex2: number | null;
28
28
  }
29
- /** What `relay` reports when it cannot say whether the transaction landed. */
30
- export declare class RelayUnknown extends WalletError {
29
+ /** What `relay` reports while the relayer is relaying a request with the same nullifiers: the request already there
30
+ * gets the answer, and one after it finds the transaction landed. */
31
+ export declare class Relaying extends WalletError {
31
32
  }
32
33
  /** Has the relayer submit `request`. A 400 means the call reverts, which includes "already landed". */
33
34
  export declare const relay: (c: ChainConfig, request: object, feeNote?: {
package/dist/services.js CHANGED
@@ -78,23 +78,25 @@ export async function relayerFees(c) {
78
78
  const fees = new Map(Object.entries(r.body.fees).map(([t, f]) => [Number(t), { fee: BigInt(f.fee) }]));
79
79
  return { relayer: r.body.relayer, publicKey: r.body.publicKey, fees };
80
80
  }
81
- /** What `relay` reports when it cannot say whether the transaction landed. */
82
- export class RelayUnknown extends WalletError {
81
+ /** What `relay` reports while the relayer is relaying a request with the same nullifiers: the request already there
82
+ * gets the answer, and one after it finds the transaction landed. */
83
+ export class Relaying extends WalletError {
83
84
  }
84
85
  /** Has the relayer submit `request`. A 400 means the call reverts, which includes "already landed". */
85
86
  export const relay = (c, request, feeNote) => relayed(`${c.transactRelayer}/transact`, { request, feeNote });
86
87
  /** Has the relayer submit a merge's `request`, as `relay` does a JoinSplit's. Answers the merged note's index. */
87
88
  export const relayMerge = (c, request) => relayed(`${c.transactRelayer}/merge`, { request });
88
89
  async function relayed(url, body) {
89
- // the relayer answers once mined, after fee bumping if need be
90
- const r = await post(url, body, 15 * 60_000).catch((e) => {
91
- throw new RelayUnknown('unavailable', `transact-relayer: ${e.message}`);
92
- });
90
+ // the relayer answers once mined, after fee bumping if need be. Without an answer it may have sent it all the same:
91
+ // the same request goes again next sync, and lands at most once
92
+ const r = await post(url, body, 15 * 60_000);
93
93
  if (typeof r.body !== 'string')
94
94
  return r.body;
95
95
  if (r.status === 400)
96
96
  return { reverted: r.body };
97
- throw new RelayUnknown('unavailable', `transact-relayer: HTTP ${r.status}: ${r.body}`);
97
+ if (r.status === 409)
98
+ throw new Relaying('unavailable', `transact-relayer: ${r.body}`);
99
+ return fail('unavailable', `transact-relayer: HTTP ${r.status}: ${r.body}`);
98
100
  }
99
101
  /** Sends a merge's witness (JSON, of `inputs` inputs) sealed to the merge prover's enclave, to prove `statement` (the
100
102
  * attester's `Merge`). Proving takes minutes: `proofOf` tells when it is done. */
@@ -1 +1 @@
1
- {"version":3,"file":"services.js","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH,+GAA+G;AAC/G,kCAAkC;AAElC,OAAO,EAA0B,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AAC/F,OAAO,EAA0C,WAAW,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAwC,cAAc,EAAgB,MAAM,cAAc,CAAC;AAElG;qDACqD;AACrD,MAAM,WAAW,GAAG,EAAE,GAAG,IAAI,CAAC;AAC9B,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,CAAC;AAE3B,MAAM,IAAI,GAAG,CAAC,CAAU,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtH,KAAK,UAAU,IAAI,CACjB,GAAW,EACX,IAA2C;IAE3C,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,IAAI,CAAC,aAAa,EAAE,GAAG,GAAG,KAAM,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACvD,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,IAAI,GAAG,CAAI,GAAW,EAAE,IAAa,EAAE,SAAkB,EAAE,EAAE,CACjE,IAAI,CAAI,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;AAEjH,MAAM,MAAM,GAAG,CAAC,CAAS,EAAc,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3F,4FAA4F;AAC5F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAc,EAAuB,EAAE,CACjE,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,QAAQ,cAAc,EAAE,CAAC,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;AAE1E;sDACsD;AACtD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,CAAc,EAAE,GAAW,EAAE,KAAoB,EAAE,IAAY;IAC9F,MAAM,CAAC,GAAG,MAAM,IAAI,CAAiD,GAAG,CAAC,CAAC;IAC1E,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,aAAa,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACjF,IAAI,aAAa,IAAI,KAAK,EAAE,CAAC;QAC3B,gHAAgH;QAChH,YAAY;QACZ,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,cAAc;YAC5D,OAAO,IAAI,CAAC,qBAAqB,EAAE,iBAAiB,IAAI,sBAAsB,CAAC,CAAC;QAClF,OAAO,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC,qBAAqB,EAAE,OAAO,IAAI,+BAA+B,CAAC,CAAC;IACxG,IAAI,CAAC;QACH,qDAAqD;QACrD,OAAO,IAAI,CAAC,iBAAiB,CAC3B,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,EAC1B,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,IAAI,EACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CACtC,CAAC;IACJ,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,IAAI,CAAC,qBAAqB,EAAE,OAAO,IAAI,kCAAmC,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1G,CAAC;AACH,CAAC;AAED;8CAC8C;AAC9C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,CAAc,EAAE,SAAiB,EAAE,OAAe,EAAE,MAAe;IAC9F,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,CAAC;IACrG,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAChF,MAAM,CAAC,GAAG,MAAM,IAAI,CAAuB,GAAG,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;QAC5G,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;QAC1D,yEAAyE;QACzE,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,0BAA0B,CAAC,IAAI,OAAO,KAAK,CAAC;YAAE,SAAS;QAC3E,kGAAkG;QAClG,IAAI,CAAC,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,mBAAmB,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9E,2EAA2E;QAC3E,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,CAAc;IAC9C,MAAM,CAAC,GAAG,MAAM,IAAI,CAClB,GAAG,CAAC,CAAC,eAAe,MAAM,CAC3B,CAAC;IACF,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1F,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvG,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;AACxE,CAAC;AAUD,8EAA8E;AAC9E,MAAM,OAAO,YAAa,SAAQ,WAAW;CAAG;AAEhD,uGAAuG;AACvG,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,CAAc,EACd,OAAe,EACf,OAAsC,EACG,EAAE,CAAC,OAAO,CAAU,GAAG,CAAC,CAAC,eAAe,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAEtH,kHAAkH;AAClH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,CAAc,EACd,OAAe,EAC2E,EAAE,CAC5F,OAAO,CAAC,GAAG,CAAC,CAAC,eAAe,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AAErD,KAAK,UAAU,OAAO,CAAI,GAAW,EAAE,IAAY;IACjD,+DAA+D;IAC/D,MAAM,CAAC,GAAG,MAAM,IAAI,CAAI,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE;QACnE,MAAM,IAAI,YAAY,CAAC,aAAa,EAAE,qBAAsB,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC;IAC9C,IAAI,CAAC,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,IAAI,YAAY,CAAC,aAAa,EAAE,0BAA0B,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACzF,CAAC;AAID;kFACkF;AAClF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,CAAc,EACd,CAAc,EACd,SAAiB,EACjB,OAAe,EACf,MAAc;IAEd,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC;IACnF,MAAM,CAAC,GAAG,MAAM,IAAI,CAAqB,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;IACpG,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAChH,CAAC;AAED;8GAC8G;AAC9G,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,CAAc,EACd,QAAa;IAEb,MAAM,CAAC,GAAG,MAAM,IAAI,CAClB,GAAG,CAAC,CAAC,GAAG,WAAW,QAAQ,EAAE,CAC9B,CAAC;IACF,IAAI,CAAC,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACtF,OAAO,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC;AAED;+CAC+C;AAC/C,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAY,EAAE,cAAuB;IACxE,MAAM,CAAC,GAAG,MAAM,IAAI,CAAe,GAAG,KAAK,CAAC,MAAM,CAAC,cAAc,UAAU,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;IACjG,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACzF,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED;;;6GAG6G;AAC7G,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAY,EACZ,KAAc,EACd,WAAgB,EAChB,MAAc,EACd,WAAqB,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,EAAE;IAErD,MAAM,CAAC,GAAG,MAAM,IAAI,CAClB,GAAG,KAAK,CAAC,MAAM,CAAC,cAAc,WAAW,EACzC;QACE,KAAK;QACL,WAAW;QACX,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QAC9F,QAAQ,EAAE,QAAQ,CAAC,OAAO;QAC1B,WAAW,EAAE,QAAQ,CAAC,GAAG;KAC1B,CACF,CAAC;IACF,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACzF,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC;IAChD,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,2EAA2E;IAC3E,IAAI,GAAG,IAAI,EAAE,IAAI,IAAI;QAAE,IAAI,CAAC,aAAa,EAAE,6BAA6B,GAAG,EAAE,CAAC,CAAC;IAC/E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,qBAAqB,CAChD,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,CAC3F,CAAC;IACF,IAAI,cAAc,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE;QACzD,IAAI,CAAC,kBAAkB,EAAE,4BAA4B,cAAc,uBAAuB,QAAQ,EAAE,CAAC,CAAC;IACxG,OAAO,EAAE,cAAc,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC;AAC3E,CAAC"}
1
+ {"version":3,"file":"services.js","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"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"}
package/dist/spend.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type Recipient } from '@zerc20/storage-sdk';
2
2
  import { type Address, type Hex } from 'viem';
3
3
  import { type Ctx } from './context.js';
4
4
  import type { Indexer } from './servers.js';
5
- import { type OperationRecord, type Position, type SpendOperation } from './state.js';
5
+ import { type NoteRecord, type OperationRecord, type Position, type SpendOperation } from './state.js';
6
6
  /** Something spendable. */
7
7
  export interface Coin {
8
8
  kind: 'note' | 'burn';
@@ -41,9 +41,14 @@ export interface Step {
41
41
  attempts: number;
42
42
  /** Once relayed: the block the relayer saw it mined in. */
43
43
  relayedBlock?: number;
44
- /** The indexer's nullifier count once it is past that block, or just after a relay that found it landed already.
45
- * Final nullifiers past it, and the input's still not among them, means a reorg dropped the transaction. */
44
+ /** The indexer's nullifier count once it is past that block, or just after a relay that reverted on an input spent
45
+ * already: that leaves the step `attested`, not relayed again until its change tells whose transaction spent it.
46
+ * Final nullifiers past it, and its inputs still not among them, means whatever spent them is gone: a reorg dropped
47
+ * it, or the relayer never sent it. */
46
48
  relayedAt?: number;
49
+ /** The final commitment count once an input is seen spent. Final commitments past it, and the change still not among
50
+ * them, means another transaction spent the input, and this step can never land. */
51
+ spentAt?: number;
47
52
  }
48
53
  /** The most inputs one merge spends: the attester takes requests up to 2 MB, at 8 KiB of sealed witness per input
49
54
  * sent as hex, and the prover folds them in one job. */
@@ -65,9 +70,13 @@ export interface MergeStep {
65
70
  /** Refused by the prover, or reverting with no sign of it on chain: built again with the same output a few times,
66
71
  * then failed. */
67
72
  attempts: number;
68
- /** As a JoinSplit's. */
73
+ /** Built again after a relay of it reverted, which counts in `attempts` beside the prover's refusals: the request
74
+ * out may still land, so the step is not cancelled. */
75
+ sent?: true;
76
+ /** As a JoinSplit's, with `proved` where a JoinSplit is `attested`. */
69
77
  relayedBlock?: number;
70
78
  relayedAt?: number;
79
+ spentAt?: number;
71
80
  }
72
81
  /** Where a send or withdrawal goes; `self` merges the wallet's own notes and nothing leaves. */
73
82
  export type Destination = {
@@ -157,10 +166,15 @@ export declare const codeOf: (code: string) => import("@zerc20/storage-sdk").Rec
157
166
  /** What `step` paid besides `op`'s amount, the relayer's fee and the protocol's cut: what its inputs hold beyond the
158
167
  * amount and the change. */
159
168
  export declare const feeOf: (op: SpendOperation, step: Step) => bigint;
160
- /** Where a relayed JoinSplit stands, by its first input `f` as sync last saw it and `relayedAt`, a nullifier count past
161
- * its own (`undefined` while not known): spent, or the final nullifiers passed that count without its own (a reorg
162
- * dropped it, or the relayer never sent it), or neither yet. */
163
- export declare function relayedState(f: Position | undefined, relayedAt: number | undefined): 'landed' | 'dropped' | 'waiting';
169
+ /** Where a relayed step stands, by its `change` (a merge's note) and its `inputs` as sync last saw them:
170
+ * - `landed`: the change is final where this wallet expects it;
171
+ * - `taken`: an input is spent and the final commitments passed `spentAt` without the change, so another transaction
172
+ * spent it, and this step can never land;
173
+ * - `dropped`: no input is spent and the final nullifiers passed `relayedAt` without them: whatever was spending them
174
+ * is gone, a reorg dropped it or the relayer never sent it;
175
+ * - `mined`: the change is in a block, not final yet;
176
+ * - `waiting`: none of these yet. */
177
+ export declare function relayedState(change: Pick<NoteRecord, 'found' | 'watch'> | undefined, inputs: (Position | undefined)[], relayedAt: number | undefined, spentAt: number | undefined): 'landed' | 'taken' | 'dropped' | 'mined' | 'waiting';
164
178
  /** The indexer's nullifier count once it is past `block`, so past the nullifiers of a transaction mined there unless a
165
179
  * reorg moved them; `undefined` until then. The relayer answers once the block is mined, which may be before the
166
180
  * indexer has read it. */
@@ -168,5 +182,12 @@ export declare function nullifiersPast(indexer: Pick<Indexer, 'status'>, block:
168
182
  /** Whether `op`'s payment or withdrawal is on chain: done, or relayed, which the relayer answers once it is mined. Until
169
183
  * it is final a reorg may still drop it, and the operation relays it again. */
170
184
  export declare function landed(op: SpendOperation): boolean;
171
- /** Takes `op` as far as it can go now, saving before each step that leaves the device. */
185
+ /** Whether `op` can still be cancelled, having paid nothing: a payment, withdrawal or consolidation under way with no
186
+ * request out to the relayer, and no merge of it landed. A step at its relay stage has one out, as its request leaves
187
+ * the moment it gets there; so has one built again after a relay of it reverted, as that request may still land: a
188
+ * JoinSplit's `attempts` count only those, and a merge, whose `attempts` count its prover's refusals too, is `sent`. */
189
+ export declare function cancellable(op: OperationRecord): boolean;
190
+ /** Takes `op` as far as it can go now, saving before each step that leaves the device. One cancelled meanwhile
191
+ * (`cancel`) finishes the stage under way and starts no other: its status is looked at after each, and nothing it does
192
+ * later is kept, as it is out of the state. A relayer that is relaying its request already throws `Relaying`. */
172
193
  export declare function advanceSpend(ctx: Ctx, op: SpendOperation): Promise<void>;
package/dist/spend.js CHANGED
@@ -6,9 +6,12 @@
6
6
  // and deposits into one note to self, proved by the merge prover (a wallet cannot: its key is 6.3 GB). More take
7
7
  // another merge, once a root is over the first one's note.
8
8
  //
9
- // A relayed JoinSplit or merge is done once its first input's nullifier is final, as sync sees it: the relayer's
10
- // answer alone could be a lie, or a reorg could drop the transaction. Only one that cannot land fails, which frees its
11
- // inputs: one whose nullifier is nowhere on chain, after it kept reverting.
9
+ // A relayed JoinSplit or merge is done once its change (a merge's note) is final where this wallet expects it, as sync
10
+ // sees it: only its own transaction puts that note there, as its salt is fresh and the proof binds it to the inputs'
11
+ // nullifiers. The relayer's answer alone could be a lie, a reorg could drop the transaction, and a spent input alone
12
+ // could be another device's doing: each device of a wallet locks its inputs in its own state. Only one that cannot
13
+ // land fails, which frees its inputs: one that kept reverting with no input spent, or one whose input another
14
+ // transaction spent.
12
15
  import { commit, decodeCode, planPayCode, planSelf, planSend } from '@zerc20/storage-sdk';
13
16
  import { bytesToHex, decodeAbiParameters, hexToBytes, numberToHex, parseAbiParameters, } from 'viem';
14
17
  import { UNWRAP_INTENT } from './chain.js';
@@ -17,7 +20,7 @@ import { core } from './core.js';
17
20
  import { fail } from './errors.js';
18
21
  import { prove } from './prove.js';
19
22
  import { scan } from './scan.js';
20
- import { RelayUnknown, attest, proofOf, relay, relayMerge, relayerFees, requestProof, } from './services.js';
23
+ import { attest, proofOf, relay, relayMerge, relayerFees, requestProof } from './services.js';
21
24
  import { unspent } from './state.js';
22
25
  import { commitmentsAt, noteReceipt, storageState, takeRefresh, tokenIds } from './sync.js';
23
26
  import { aspPath, aspPaths, commitmentPath, commitmentPaths } from './tree.js';
@@ -474,29 +477,65 @@ const positionsAt = (ctx, chainId, index) => [...ctx.state.notes, ...ctx.state.d
474
477
  .flatMap((r) => r.found.filter((f) => f.index === index));
475
478
  /** The final one of them: an input is final. */
476
479
  const positionOf = (ctx, chainId, index) => positionsAt(ctx, chainId, index).find((f) => !f.tentative);
477
- /** Whether the step's first input is spent on chain: among the final nullifiers (as sync last saw them), or among the
478
- * newer ones. Nothing but the step spends a locked input (rage exit refuses one, and a wallet is open in one tab), so
479
- * a spent input is the step landing. Reads the pages everyone polls, never asks about the nullifier. */
480
- async function inputSpent(ctx, op, step) {
481
- const f = positionOf(ctx, op.chainId, step.inputs[0].index);
482
- if (f?.spent)
480
+ /** Whether any of the step's inputs is spent on chain: among the final nullifiers (as sync last saw them), or among
481
+ * the newer ones. Reads the pages everyone polls, never asks about a nullifier. */
482
+ async function inputsSpent(ctx, op, step) {
483
+ const inputs = step.inputs.map((coin) => positionOf(ctx, op.chainId, coin.index));
484
+ if (inputs.some((f) => f?.spent))
483
485
  return true;
484
- if (!f?.nullifier)
486
+ // copies: sync alone moves the watches, over final entries
487
+ const watches = inputs.flatMap((f) => (f?.nullifier ? [{ ...f.nullifier }] : []));
488
+ if (watches.length === 0)
485
489
  return false;
486
490
  const c = chainCtx(ctx, op.chainId);
487
491
  const { nullifiers } = await c.indexer.status();
488
- // a copy: sync alone moves the watch, over final entries
489
- const watch = { ...f.nullifier };
490
- const hits = await scan([watch], 8, (p) => c.indexer.page('nullifiers', p), watch.scannedTo, nullifiers);
492
+ const from = Math.min(...watches.map((w) => w.scannedTo));
493
+ const hits = await scan(watches, 8, (p) => c.indexer.page('nullifiers', p), from, nullifiers);
491
494
  return hits.length > 0;
492
495
  }
493
- /** Where a relayed JoinSplit stands, by its first input `f` as sync last saw it and `relayedAt`, a nullifier count past
494
- * its own (`undefined` while not known): spent, or the final nullifiers passed that count without its own (a reorg
495
- * dropped it, or the relayer never sent it), or neither yet. */
496
- export function relayedState(f, relayedAt) {
497
- if (f?.spent)
496
+ /** Where a relayed step stands, by its `change` (a merge's note) and its `inputs` as sync last saw them:
497
+ * - `landed`: the change is final where this wallet expects it;
498
+ * - `taken`: an input is spent and the final commitments passed `spentAt` without the change, so another transaction
499
+ * spent it, and this step can never land;
500
+ * - `dropped`: no input is spent and the final nullifiers passed `relayedAt` without them: whatever was spending them
501
+ * is gone, a reorg dropped it or the relayer never sent it;
502
+ * - `mined`: the change is in a block, not final yet;
503
+ * - `waiting`: none of these yet. */
504
+ export function relayedState(change, inputs, relayedAt, spentAt) {
505
+ const seen = change?.found.filter((f) => f.confirmed) ?? [];
506
+ if (seen.some((f) => !f.tentative))
498
507
  return 'landed';
499
- return f?.nullifier && relayedAt !== undefined && f.nullifier.scannedTo >= relayedAt ? 'dropped' : 'waiting';
508
+ if (inputs.some((f) => f?.spent)) {
509
+ // a final position not read yet may be the change
510
+ const missed = spentAt !== undefined &&
511
+ !!change?.watch &&
512
+ change.watch.scannedTo >= spentAt &&
513
+ !change.found.some((f) => !f.tentative);
514
+ if (missed)
515
+ return 'taken';
516
+ }
517
+ else {
518
+ const watches = inputs.flatMap((f) => (f?.nullifier ? [f.nullifier] : []));
519
+ if (relayedAt !== undefined && watches.length > 0 && watches.every((w) => w.scannedTo >= relayedAt))
520
+ return 'dropped';
521
+ }
522
+ return seen.length > 0 ? 'mined' : 'waiting';
523
+ }
524
+ /** `relayedState` of `step`, failing the operation once another transaction took an input. The first time an input
525
+ * is seen spent, short of the step landing, it notes the final commitment count: a later status counts no fewer, so
526
+ * once the change's watch passes it, it has looked wherever that transaction put its notes. */
527
+ async function standing(ctx, op, step) {
528
+ const inputs = step.inputs.map((coin) => positionOf(ctx, op.chainId, coin.index));
529
+ const salt = 'output' in step ? step.output.salt : step.outputs[1].salt;
530
+ const change = ctx.state.notes.find((n) => n.chainId === op.chainId && n.salt === salt);
531
+ const now = relayedState(change, inputs, step.relayedAt, step.spentAt);
532
+ if (now === 'taken')
533
+ fail('spent-elsewhere', 'another device of this wallet spent a note or deposit of it first: it paid nothing');
534
+ if (now !== 'landed' && step.spentAt === undefined && inputs.some((f) => f?.spent)) {
535
+ step.spentAt = (await chainCtx(ctx, op.chainId).indexer.status()).commitmentsFinal;
536
+ await ctx.save();
537
+ }
538
+ return now;
500
539
  }
501
540
  /** The indexer's nullifier count once it is past `block`, so past the nullifiers of a transaction mined there unless a
502
541
  * reorg moved them; `undefined` until then. The relayer answers once the block is mined, which may be before the
@@ -512,6 +551,20 @@ export async function nullifiersPast(indexer, block) {
512
551
  export function landed(op) {
513
552
  return (op.status === 'done' || (op.status === 'pending' && op.kind !== 'consolidate' && op.data.step?.stage === 'relayed'));
514
553
  }
554
+ /** Whether `op` can still be cancelled, having paid nothing: a payment, withdrawal or consolidation under way with no
555
+ * request out to the relayer, and no merge of it landed. A step at its relay stage has one out, as its request leaves
556
+ * the moment it gets there; so has one built again after a relay of it reverted, as that request may still land: a
557
+ * JoinSplit's `attempts` count only those, and a merge, whose `attempts` count its prover's refusals too, is `sent`. */
558
+ export function cancellable(op) {
559
+ if (op.status !== 'pending' || op.kind === 'deposit' || op.data.paid > 0n)
560
+ return false;
561
+ const step = op.data.step;
562
+ if (!step)
563
+ return true;
564
+ if ('output' in step)
565
+ return !step.sent && step.stage !== 'proved' && step.stage !== 'relayed';
566
+ return step.attempts === 0 && step.stage !== 'attested' && step.stage !== 'relayed';
567
+ }
515
568
  function markSpent(ctx, op, step) {
516
569
  for (const coin of step.inputs)
517
570
  for (const f of positionsAt(ctx, op.chainId, coin.index))
@@ -524,14 +577,18 @@ function placeChange(ctx, op, salt, index) {
524
577
  note.found.push({ index, tentative: true, spent: false });
525
578
  }
526
579
  }
527
- /** Takes `op` as far as it can go now, saving before each step that leaves the device. */
580
+ /** Takes `op` as far as it can go now, saving before each step that leaves the device. One cancelled meanwhile
581
+ * (`cancel`) finishes the stage under way and starts no other: its status is looked at after each, and nothing it does
582
+ * later is kept, as it is out of the state. A relayer that is relaying its request already throws `Relaying`. */
528
583
  export async function advanceSpend(ctx, op) {
529
584
  const data = op.data;
530
585
  const c = chainCtx(ctx, op.chainId);
531
586
  for (;;) {
587
+ if (op.status !== 'pending')
588
+ return;
532
589
  if (!data.step) {
533
590
  const step = await planStep(ctx, op);
534
- if (!step)
591
+ if (!step || op.status !== 'pending')
535
592
  return;
536
593
  if (step === 'done') {
537
594
  op.status = 'done';
@@ -552,6 +609,8 @@ export async function advanceSpend(ctx, op) {
552
609
  step.stage = 'noted';
553
610
  await ctx.save();
554
611
  }
612
+ if (op.status !== 'pending')
613
+ return;
555
614
  if (step.stage === 'noted') {
556
615
  const built = await witnessOf(ctx, op, step);
557
616
  if (!built)
@@ -596,6 +655,8 @@ export async function advanceSpend(ctx, op) {
596
655
  step.stage = 'proved';
597
656
  await ctx.save();
598
657
  }
658
+ if (op.status !== 'pending')
659
+ return;
599
660
  if (step.stage === 'proved') {
600
661
  const attestation = await attest(c.config, step.statement, step.witness);
601
662
  step.request = { ...step.request, attestation };
@@ -603,19 +664,25 @@ export async function advanceSpend(ctx, op) {
603
664
  step.stage = 'attested';
604
665
  await ctx.save();
605
666
  }
667
+ if (op.status !== 'pending')
668
+ return;
669
+ if (step.stage === 'attested' && step.relayedAt !== undefined) {
670
+ // a relay of it reverted on an input spent already: an earlier relay of it landed, one whose answer was lost, or
671
+ // another transaction spent the input. Relaying it again would tell nothing more
672
+ const now = await standing(ctx, op, step);
673
+ if (now === 'waiting')
674
+ return;
675
+ if (now === 'dropped')
676
+ delete step.relayedAt;
677
+ else
678
+ step.stage = 'relayed';
679
+ await ctx.save();
680
+ }
606
681
  if (step.stage === 'attested') {
607
682
  const relayerOut = step.outputs.find((o) => o.role === 'relayer');
608
- let result;
609
- try {
610
- result = await relay(c.config, step.request, relayerOut ? { value: relayerOut.value, salt: relayerOut.salt } : undefined);
611
- }
612
- catch (e) {
613
- if (e instanceof RelayUnknown)
614
- return; // try the same request again next time
615
- throw e;
616
- }
683
+ const result = await relay(c.config, step.request, relayerOut ? { value: relayerOut.value, salt: relayerOut.salt } : undefined);
617
684
  if ('reverted' in result) {
618
- if (!(await inputSpent(ctx, op, step))) {
685
+ if (!(await inputsSpent(ctx, op, step))) {
619
686
  // stale roots, most likely: prove again with the same outputs, next sync. A JoinSplit that keeps reverting
620
687
  // is not on chain and cannot land, so failing it frees its inputs.
621
688
  if (++step.attempts > 3)
@@ -624,24 +691,24 @@ export async function advanceSpend(ctx, op) {
624
691
  await ctx.save();
625
692
  return;
626
693
  }
627
- // an earlier relay of it landed, one whose answer was lost, and the indexer counts its nullifier already
694
+ // the indexer counts the nullifiers of whatever spent it already
628
695
  step.relayedAt = (await c.indexer.status()).nullifiers;
696
+ await ctx.save();
697
+ return;
629
698
  }
630
- else {
631
- placeChange(ctx, op, step.outputs[1].salt, result.noteIndex2);
632
- data.txHashes = [...(data.txHashes ?? []), result.txHash];
633
- step.relayedBlock = result.block;
634
- delete step.relayedAt;
635
- }
699
+ placeChange(ctx, op, step.outputs[1].salt, result.noteIndex2);
700
+ data.txHashes = [...(data.txHashes ?? []), result.txHash];
701
+ step.relayedBlock = result.block;
636
702
  step.stage = 'relayed';
637
703
  await ctx.save();
638
704
  }
639
705
  if (step.stage === 'relayed') {
640
706
  step.relayedAt ??= await nullifiersPast(c.indexer, step.relayedBlock);
641
- const now = relayedState(positionOf(ctx, op.chainId, step.inputs[0].index), step.relayedAt);
707
+ const now = await standing(ctx, op, step);
642
708
  if (now === 'dropped') {
643
709
  // relay it again next sync; if it did land after all, that reverts and finds it
644
710
  step.stage = 'attested';
711
+ delete step.relayedAt;
645
712
  await ctx.save();
646
713
  }
647
714
  if (now !== 'landed')
@@ -674,6 +741,8 @@ async function advanceMerge(ctx, op, step) {
674
741
  step.stage = 'noted';
675
742
  await ctx.save();
676
743
  }
744
+ if (op.status !== 'pending')
745
+ return false;
677
746
  if (step.stage === 'noted') {
678
747
  const built = await mergeWitnessOf(ctx, op, step);
679
748
  if (!built)
@@ -708,6 +777,8 @@ async function advanceMerge(ctx, op, step) {
708
777
  step.stage = 'attested';
709
778
  await ctx.save();
710
779
  }
780
+ if (op.status !== 'pending')
781
+ return false;
711
782
  if (step.stage === 'attested') {
712
783
  await requestProof(c.config, prover, step.statement, step.witness, step.inputs.length);
713
784
  delete step.witness; // the prover has it: the inputs' openings need not stay saved here
@@ -731,37 +802,43 @@ async function advanceMerge(ctx, op, step) {
731
802
  step.stage = 'proved';
732
803
  await ctx.save();
733
804
  }
805
+ if (op.status !== 'pending')
806
+ return false;
807
+ if (step.stage === 'proved' && step.relayedAt !== undefined) {
808
+ // as a JoinSplit's, told apart by its note
809
+ const now = await standing(ctx, op, step);
810
+ if (now === 'waiting')
811
+ return false;
812
+ if (now === 'dropped')
813
+ delete step.relayedAt;
814
+ else
815
+ step.stage = 'relayed';
816
+ await ctx.save();
817
+ }
734
818
  if (step.stage === 'proved') {
735
- let result;
736
- try {
737
- result = await relayMerge(c.config, step.request);
738
- }
739
- catch (e) {
740
- if (e instanceof RelayUnknown)
741
- return false; // try the same request again next time
742
- throw e;
743
- }
819
+ const result = await relayMerge(c.config, step.request);
744
820
  if ('reverted' in result) {
745
821
  // stale roots, most likely: built again with the same output, next sync
746
- if (!(await inputSpent(ctx, op, step)))
822
+ if (!(await inputsSpent(ctx, op, step))) {
823
+ step.sent = true;
747
824
  return again(result.reverted);
748
- // an earlier relay of it landed, one whose answer was lost, and the indexer counts its nullifiers already
825
+ }
749
826
  step.relayedAt = (await c.indexer.status()).nullifiers;
827
+ await ctx.save();
828
+ return false;
750
829
  }
751
- else {
752
- placeChange(ctx, op, step.output.salt, result.noteIndex);
753
- data.txHashes = [...(data.txHashes ?? []), result.txHash];
754
- step.relayedBlock = result.block;
755
- delete step.relayedAt;
756
- }
830
+ placeChange(ctx, op, step.output.salt, result.noteIndex);
831
+ data.txHashes = [...(data.txHashes ?? []), result.txHash];
832
+ step.relayedBlock = result.block;
757
833
  step.stage = 'relayed';
758
834
  await ctx.save();
759
835
  }
760
836
  step.relayedAt ??= await nullifiersPast(c.indexer, step.relayedBlock);
761
- const now = relayedState(positionOf(ctx, op.chainId, step.inputs[0].index), step.relayedAt);
837
+ const now = await standing(ctx, op, step);
762
838
  if (now === 'dropped') {
763
839
  // relay it again next sync; if it did land after all, that reverts and finds it
764
840
  step.stage = 'proved';
841
+ delete step.relayedAt;
765
842
  await ctx.save();
766
843
  }
767
844
  if (now !== 'landed')