@inversealtruism/cairn-sdk 0.8.0 → 0.8.2

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/connect.cjs CHANGED
@@ -30,7 +30,7 @@ __export(connect_exports, {
30
30
  module.exports = __toCommonJS(connect_exports);
31
31
 
32
32
  // src/errors.ts
33
- var SDK_VERSION = "0.8.0";
33
+ var SDK_VERSION = "0.8.2";
34
34
  var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
35
35
  var CairnError = class extends Error {
36
36
  /** Stable machine code for programmatic branching. */
@@ -95,17 +95,22 @@ var UnsupportedMethodError = class extends CairnError {
95
95
  }
96
96
  };
97
97
  var SubmitInFlightError = class extends CairnError {
98
- /** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
98
+ /** The locally-computed consensus txid to reconcile against the chain (undefined when it is not known). */
99
99
  txid;
100
- /** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
100
+ /** The signed transaction, set only when `Chain.send` threw this (a wallet write keeps it in the wallet).
101
+ * `chain.submit(nodeJson)` resends it unchanged: the same coins under the same txid, so it cannot pay twice,
102
+ * where a new send may select other coins. */
103
+ nodeJson;
104
+ /** Always true: the tx MAY be in the mempool. Reconcile via `txid` or the balance; never blind-retry. */
101
105
  maybeSent = true;
102
- constructor(message, code, txid) {
103
- super(message, { code, docsPath: "/errors#submit-inflight", retryable: false });
106
+ constructor(message, code, txid, cause, nodeJson) {
107
+ super(message, { code, docsPath: "/errors#submit-inflight", retryable: false, cause });
104
108
  this.txid = txid;
109
+ this.nodeJson = nodeJson;
105
110
  }
106
111
  };
107
112
  var NATIVE_CODES = /* @__PURE__ */ new Set(["FIRST_PARTY_ONLY", "RATE_LIMITED", "FORBIDDEN", "INTERNAL"]);
108
- var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN", "WALLET_UNAVAILABLE", "REVIEW_REQUIRED"]);
113
+ var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN", "WALLET_UNAVAILABLE", "REVIEW_REQUIRED", "SEAL_NOT_MINED"]);
109
114
  var NESTED_TERMINAL = /* @__PURE__ */ new Set([
110
115
  "FILL_UNSAFE",
111
116
  "FILL_WRONG_TARGET",
@@ -127,7 +132,9 @@ var NESTED_TERMINAL = /* @__PURE__ */ new Set([
127
132
  "OFFER_SOURCE_CONFLICT",
128
133
  "FILL_LEGACY_SUNSET",
129
134
  "VERIFY_HORIZON",
130
- "VERIFY_UNREPRESENTABLE"
135
+ "VERIFY_UNREPRESENTABLE",
136
+ "RESERVATION_DENIED",
137
+ "RPC_BLOCK_MISMATCH"
131
138
  ]);
132
139
  function mapProviderError(error, code) {
133
140
  if (code === "USER_REJECTED" || code === "APPROVAL_CLOSED") return new UserRejectedError(error);
@@ -145,11 +152,15 @@ function mapProviderError(error, code) {
145
152
  function mapSubmitResultError(result) {
146
153
  const code = result?.code;
147
154
  const msg = typeof result?.error === "string" && result.error || code || "The wallet refused the transaction.";
148
- if (code === "SUBMIT_MAYBE_INFLIGHT" || code === "SUBMIT_DUPLICATE") {
155
+ if (code === "SUBMIT_MAYBE_INFLIGHT" || code === "SUBMIT_DUPLICATE" || code === "WALLET_UNAVAILABLE") {
149
156
  return new SubmitInFlightError(msg, code, typeof result?.txid === "string" ? result.txid : void 0);
150
157
  }
151
158
  return mapProviderError(msg, code);
152
159
  }
160
+ function mapWriteProviderError(error, code) {
161
+ if (code === "WALLET_UNAVAILABLE") return new SubmitInFlightError(error || code, code);
162
+ return mapProviderError(error, code);
163
+ }
153
164
 
154
165
  // src/connect.ts
155
166
  var DEFAULT_PROVIDER_TIMEOUT_MS = 10 * 6e4;
@@ -223,7 +234,8 @@ function unwrap(reply) {
223
234
  throw mapProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
224
235
  }
225
236
  function unwrapWrite(reply) {
226
- const result = unwrap(reply);
237
+ if (!reply || !reply.ok) throw mapWriteProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
238
+ const result = reply.result;
227
239
  if (result && typeof result === "object" && result.ok === false) {
228
240
  throw mapSubmitResultError(result);
229
241
  }
@@ -239,10 +251,12 @@ var WalletConnection = class {
239
251
  this.provider = provider;
240
252
  this._timeoutMs = opts.timeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
241
253
  }
242
- // B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed CairnError
243
- // (retryable:false: a write may already have reached the wallet, so never invite a blind auto-retry). The
244
- // genuine wallet resolves quickly once the user acts; this only fires on a truly hung/absent response.
245
- call(op, label) {
254
+ // B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed error
255
+ // (retryable:false). A write's request stays open in the wallet and may still be approved and sent, so its
256
+ // timeout is a maybe-sent SubmitInFlightError (WALLET_TIMEOUT, no txid); while that request stays open, a resend
257
+ // opens a second one, and approving both sends twice. The genuine wallet resolves quickly
258
+ // once the user acts; this only fires on a truly hung/absent response.
259
+ call(op, label, write = false) {
246
260
  const ms = this._timeoutMs;
247
261
  const invoke = () => new Promise((resolve) => {
248
262
  resolve(op());
@@ -250,7 +264,7 @@ var WalletConnection = class {
250
264
  if (!Number.isFinite(ms) || ms <= 0) return invoke();
251
265
  let timer;
252
266
  const timeout = new Promise((_, reject) => {
253
- timer = setTimeout(() => reject(new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
267
+ timer = setTimeout(() => reject(write ? new SubmitInFlightError(`the wallet did not respond to ${label} within ${ms}ms; the request may still be open in the wallet and be sent if approved, so reject it in the wallet and check the chain before sending it again`, "WALLET_TIMEOUT") : new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
254
268
  });
255
269
  return Promise.race([invoke(), timeout]).finally(() => clearTimeout(timer));
256
270
  }
@@ -326,7 +340,7 @@ var WalletConnection = class {
326
340
  return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
327
341
  }
328
342
  this.adviseFillSpvCapability();
329
- return this.call(() => this.provider.fillOffer(params), "fillOffer").then(unwrapWrite);
343
+ return this.call(() => this.provider.fillOffer(params), "fillOffer", true).then(unwrapWrite);
330
344
  }
331
345
  // P75-5 MF-10: ONE capability advisory per connection. The wallet's on-device fill-SPV (0.2.60+) is the
332
346
  // payment-grade fill boundary; when the provider SELF-REPORTS an older version, warn the integrator once
@@ -374,26 +388,26 @@ var WalletConnection = class {
374
388
  }
375
389
  /** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
376
390
  send(params) {
377
- return this.call(() => this.provider.send(params), "send").then(unwrapWrite);
391
+ return this.call(() => this.provider.send(params), "send", true).then(unwrapWrite);
378
392
  }
379
393
  /** Low-level Propose (always prompts). The caller computes the payload hash, uri and expiry epoch. */
380
394
  propose(params) {
381
- return this.call(() => this.provider.propose(params), "propose").then(unwrapWrite);
395
+ return this.call(() => this.provider.propose(params), "propose", true).then(unwrapWrite);
382
396
  }
383
397
  /** Attest/support a proposal (always prompts). */
384
398
  attest(params) {
385
- return this.call(() => this.provider.attest(params), "attest").then(unwrapWrite);
399
+ return this.call(() => this.provider.attest(params), "attest", true).then(unwrapWrite);
386
400
  }
387
401
  /** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
388
402
  sealClaim(params) {
389
- return this.call(() => this.provider.sealClaim(params), "sealClaim").then(unwrapWrite);
403
+ return this.call(() => this.provider.sealClaim(params), "sealClaim", true).then(unwrapWrite);
390
404
  }
391
405
  /** Reveal a previously sealed claim by its sealing txid (always prompts). */
392
406
  revealClaim(txid) {
393
407
  if (typeof txid !== "string" || !txid) {
394
408
  return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
395
409
  }
396
- return this.call(() => this.provider.revealClaim(txid), "revealClaim").then(unwrapWrite);
410
+ return this.call(() => this.provider.revealClaim(txid), "revealClaim", true).then(unwrapWrite);
397
411
  }
398
412
  };
399
413
  async function getWallet(opts = {}) {
package/dist/connect.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  discoverProviders,
6
6
  getWallet,
7
7
  isInstalled
8
- } from "./chunk-WWO3FXHG.js";
9
- import "./chunk-UAPWZZQK.js";
8
+ } from "./chunk-EVEBQSDK.js";
9
+ import "./chunk-37OHCH7R.js";
10
10
  export {
11
11
  WalletConnection,
12
12
  connect,
package/dist/content.cjs CHANGED
@@ -26,7 +26,7 @@ module.exports = __toCommonJS(content_exports);
26
26
  var import_csd_codec = require("@inversealtruism/csd-codec");
27
27
 
28
28
  // src/errors.ts
29
- var SDK_VERSION = "0.8.0";
29
+ var SDK_VERSION = "0.8.2";
30
30
  var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
31
31
  var CairnError = class extends Error {
32
32
  /** Stable machine code for programmatic branching. */
package/dist/content.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ContentClient
3
- } from "./chunk-DTPADO7N.js";
4
- import "./chunk-UAPWZZQK.js";
3
+ } from "./chunk-2RR4ZJPQ.js";
4
+ import "./chunk-37OHCH7R.js";
5
5
  export {
6
6
  ContentClient
7
7
  };
@@ -25,7 +25,7 @@ __export(controller_exports, {
25
25
  module.exports = __toCommonJS(controller_exports);
26
26
 
27
27
  // src/errors.ts
28
- var SDK_VERSION = "0.8.0";
28
+ var SDK_VERSION = "0.8.2";
29
29
  var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
30
30
  var CairnError = class extends Error {
31
31
  /** Stable machine code for programmatic branching. */
@@ -90,17 +90,22 @@ var UnsupportedMethodError = class extends CairnError {
90
90
  }
91
91
  };
92
92
  var SubmitInFlightError = class extends CairnError {
93
- /** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
93
+ /** The locally-computed consensus txid to reconcile against the chain (undefined when it is not known). */
94
94
  txid;
95
- /** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
95
+ /** The signed transaction, set only when `Chain.send` threw this (a wallet write keeps it in the wallet).
96
+ * `chain.submit(nodeJson)` resends it unchanged: the same coins under the same txid, so it cannot pay twice,
97
+ * where a new send may select other coins. */
98
+ nodeJson;
99
+ /** Always true: the tx MAY be in the mempool. Reconcile via `txid` or the balance; never blind-retry. */
96
100
  maybeSent = true;
97
- constructor(message, code, txid) {
98
- super(message, { code, docsPath: "/errors#submit-inflight", retryable: false });
101
+ constructor(message, code, txid, cause, nodeJson) {
102
+ super(message, { code, docsPath: "/errors#submit-inflight", retryable: false, cause });
99
103
  this.txid = txid;
104
+ this.nodeJson = nodeJson;
100
105
  }
101
106
  };
102
107
  var NATIVE_CODES = /* @__PURE__ */ new Set(["FIRST_PARTY_ONLY", "RATE_LIMITED", "FORBIDDEN", "INTERNAL"]);
103
- var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN", "WALLET_UNAVAILABLE", "REVIEW_REQUIRED"]);
108
+ var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN", "WALLET_UNAVAILABLE", "REVIEW_REQUIRED", "SEAL_NOT_MINED"]);
104
109
  var NESTED_TERMINAL = /* @__PURE__ */ new Set([
105
110
  "FILL_UNSAFE",
106
111
  "FILL_WRONG_TARGET",
@@ -122,7 +127,9 @@ var NESTED_TERMINAL = /* @__PURE__ */ new Set([
122
127
  "OFFER_SOURCE_CONFLICT",
123
128
  "FILL_LEGACY_SUNSET",
124
129
  "VERIFY_HORIZON",
125
- "VERIFY_UNREPRESENTABLE"
130
+ "VERIFY_UNREPRESENTABLE",
131
+ "RESERVATION_DENIED",
132
+ "RPC_BLOCK_MISMATCH"
126
133
  ]);
127
134
  function mapProviderError(error, code) {
128
135
  if (code === "USER_REJECTED" || code === "APPROVAL_CLOSED") return new UserRejectedError(error);
@@ -140,11 +147,15 @@ function mapProviderError(error, code) {
140
147
  function mapSubmitResultError(result) {
141
148
  const code = result?.code;
142
149
  const msg = typeof result?.error === "string" && result.error || code || "The wallet refused the transaction.";
143
- if (code === "SUBMIT_MAYBE_INFLIGHT" || code === "SUBMIT_DUPLICATE") {
150
+ if (code === "SUBMIT_MAYBE_INFLIGHT" || code === "SUBMIT_DUPLICATE" || code === "WALLET_UNAVAILABLE") {
144
151
  return new SubmitInFlightError(msg, code, typeof result?.txid === "string" ? result.txid : void 0);
145
152
  }
146
153
  return mapProviderError(msg, code);
147
154
  }
155
+ function mapWriteProviderError(error, code) {
156
+ if (code === "WALLET_UNAVAILABLE") return new SubmitInFlightError(error || code, code);
157
+ return mapProviderError(error, code);
158
+ }
148
159
 
149
160
  // src/connect.ts
150
161
  var DEFAULT_PROVIDER_TIMEOUT_MS = 10 * 6e4;
@@ -195,7 +206,8 @@ function unwrap(reply) {
195
206
  throw mapProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
196
207
  }
197
208
  function unwrapWrite(reply) {
198
- const result = unwrap(reply);
209
+ if (!reply || !reply.ok) throw mapWriteProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
210
+ const result = reply.result;
199
211
  if (result && typeof result === "object" && result.ok === false) {
200
212
  throw mapSubmitResultError(result);
201
213
  }
@@ -211,10 +223,12 @@ var WalletConnection = class {
211
223
  this.provider = provider;
212
224
  this._timeoutMs = opts.timeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
213
225
  }
214
- // B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed CairnError
215
- // (retryable:false: a write may already have reached the wallet, so never invite a blind auto-retry). The
216
- // genuine wallet resolves quickly once the user acts; this only fires on a truly hung/absent response.
217
- call(op, label) {
226
+ // B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed error
227
+ // (retryable:false). A write's request stays open in the wallet and may still be approved and sent, so its
228
+ // timeout is a maybe-sent SubmitInFlightError (WALLET_TIMEOUT, no txid); while that request stays open, a resend
229
+ // opens a second one, and approving both sends twice. The genuine wallet resolves quickly
230
+ // once the user acts; this only fires on a truly hung/absent response.
231
+ call(op, label, write = false) {
218
232
  const ms = this._timeoutMs;
219
233
  const invoke = () => new Promise((resolve) => {
220
234
  resolve(op());
@@ -222,7 +236,7 @@ var WalletConnection = class {
222
236
  if (!Number.isFinite(ms) || ms <= 0) return invoke();
223
237
  let timer;
224
238
  const timeout = new Promise((_, reject) => {
225
- timer = setTimeout(() => reject(new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
239
+ timer = setTimeout(() => reject(write ? new SubmitInFlightError(`the wallet did not respond to ${label} within ${ms}ms; the request may still be open in the wallet and be sent if approved, so reject it in the wallet and check the chain before sending it again`, "WALLET_TIMEOUT") : new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
226
240
  });
227
241
  return Promise.race([invoke(), timeout]).finally(() => clearTimeout(timer));
228
242
  }
@@ -298,7 +312,7 @@ var WalletConnection = class {
298
312
  return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
299
313
  }
300
314
  this.adviseFillSpvCapability();
301
- return this.call(() => this.provider.fillOffer(params), "fillOffer").then(unwrapWrite);
315
+ return this.call(() => this.provider.fillOffer(params), "fillOffer", true).then(unwrapWrite);
302
316
  }
303
317
  // P75-5 MF-10: ONE capability advisory per connection. The wallet's on-device fill-SPV (0.2.60+) is the
304
318
  // payment-grade fill boundary; when the provider SELF-REPORTS an older version, warn the integrator once
@@ -346,26 +360,26 @@ var WalletConnection = class {
346
360
  }
347
361
  /** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
348
362
  send(params) {
349
- return this.call(() => this.provider.send(params), "send").then(unwrapWrite);
363
+ return this.call(() => this.provider.send(params), "send", true).then(unwrapWrite);
350
364
  }
351
365
  /** Low-level Propose (always prompts). The caller computes the payload hash, uri and expiry epoch. */
352
366
  propose(params) {
353
- return this.call(() => this.provider.propose(params), "propose").then(unwrapWrite);
367
+ return this.call(() => this.provider.propose(params), "propose", true).then(unwrapWrite);
354
368
  }
355
369
  /** Attest/support a proposal (always prompts). */
356
370
  attest(params) {
357
- return this.call(() => this.provider.attest(params), "attest").then(unwrapWrite);
371
+ return this.call(() => this.provider.attest(params), "attest", true).then(unwrapWrite);
358
372
  }
359
373
  /** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
360
374
  sealClaim(params) {
361
- return this.call(() => this.provider.sealClaim(params), "sealClaim").then(unwrapWrite);
375
+ return this.call(() => this.provider.sealClaim(params), "sealClaim", true).then(unwrapWrite);
362
376
  }
363
377
  /** Reveal a previously sealed claim by its sealing txid (always prompts). */
364
378
  revealClaim(txid) {
365
379
  if (typeof txid !== "string" || !txid) {
366
380
  return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
367
381
  }
368
- return this.call(() => this.provider.revealClaim(txid), "revealClaim").then(unwrapWrite);
382
+ return this.call(() => this.provider.revealClaim(txid), "revealClaim", true).then(unwrapWrite);
369
383
  }
370
384
  };
371
385
  async function getWallet(opts = {}) {
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  CairnController
3
- } from "./chunk-JXBO4LV6.js";
4
- import "./chunk-WWO3FXHG.js";
5
- import "./chunk-UAPWZZQK.js";
3
+ } from "./chunk-AHPXOYCC.js";
4
+ import "./chunk-EVEBQSDK.js";
5
+ import "./chunk-37OHCH7R.js";
6
6
  export {
7
7
  CairnController
8
8
  };