@inversealtruism/cairn-sdk 0.1.2 → 0.1.3
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/board.cjs +33 -2
- package/dist/board.js +2 -2
- package/dist/chain.cjs +29 -0
- package/dist/chain.d.cts +23 -2
- package/dist/chain.d.ts +23 -2
- package/dist/chain.js +5 -1
- package/dist/{chunk-6VWLWF7R.js → chunk-5KAUQH72.js} +31 -2
- package/dist/{chunk-OGP2W7DL.js → chunk-645CYBVN.js} +1 -1
- package/dist/chunk-G4OZKZSJ.js +213 -0
- package/dist/{chunk-BZVJVL4Q.js → chunk-OSXS26RA.js} +1 -1
- package/dist/chunk-UMGWVOOT.js +99 -0
- package/dist/chunk-ZAE2BIH5.js +78 -0
- package/dist/connect.cjs +145 -18
- package/dist/connect.d.cts +134 -2
- package/dist/connect.d.ts +134 -2
- package/dist/connect.js +4 -2
- package/dist/content.cjs +34 -3
- package/dist/content.js +2 -2
- package/dist/controller.cjs +337 -0
- package/dist/controller.d.cts +36 -0
- package/dist/controller.d.ts +36 -0
- package/dist/controller.js +8 -0
- package/dist/index.cjs +256 -22
- package/dist/index.d.cts +33 -4
- package/dist/index.d.ts +33 -4
- package/dist/index.js +27 -15
- package/dist/react.cjs +353 -0
- package/dist/react.d.cts +30 -0
- package/dist/react.d.ts +30 -0
- package/dist/react.js +23 -0
- package/package.json +31 -27
- package/dist/chunk-PHMU3CG5.js +0 -63
- package/dist/chunk-SMAPCKVG.js +0 -118
package/dist/board.cjs
CHANGED
|
@@ -27,10 +27,41 @@ var import_csd_codec = require("@inversealtruism/csd-codec");
|
|
|
27
27
|
var import_csd_codec2 = require("@inversealtruism/csd-codec");
|
|
28
28
|
|
|
29
29
|
// src/errors.ts
|
|
30
|
+
var SDK_VERSION = "0.1.2";
|
|
31
|
+
var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
|
|
30
32
|
var CairnError = class extends Error {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
/** Stable machine code for programmatic branching. */
|
|
34
|
+
code;
|
|
35
|
+
/** The human message without the appended docs/version footer. */
|
|
36
|
+
shortMessage;
|
|
37
|
+
/** Relative docs path (under the SDK docs base), if any. */
|
|
38
|
+
docsPath;
|
|
39
|
+
/** The cairn-sdk version that produced this error. */
|
|
40
|
+
version = SDK_VERSION;
|
|
41
|
+
constructor(shortMessage, opts = {}) {
|
|
42
|
+
const docs = opts.docsPath;
|
|
43
|
+
super(
|
|
44
|
+
[shortMessage, docs ? `Docs: ${DOCS_BASE}${docs}` : "", `Version: cairn-sdk@${SDK_VERSION}`].filter(Boolean).join("\n"),
|
|
45
|
+
{ cause: opts.cause }
|
|
46
|
+
);
|
|
33
47
|
this.name = new.target.name;
|
|
48
|
+
this.code = opts.code ?? "UNKNOWN";
|
|
49
|
+
this.shortMessage = shortMessage;
|
|
50
|
+
this.docsPath = docs;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
54
|
+
* returns the DEEPEST cause (the root). Mirrors viem's `BaseError.walk()`.
|
|
55
|
+
*/
|
|
56
|
+
walk(fn) {
|
|
57
|
+
let current = this;
|
|
58
|
+
let last = this;
|
|
59
|
+
while (current != null) {
|
|
60
|
+
if (fn && fn(current)) return current;
|
|
61
|
+
last = current;
|
|
62
|
+
current = current?.cause;
|
|
63
|
+
}
|
|
64
|
+
return fn ? null : last;
|
|
34
65
|
}
|
|
35
66
|
};
|
|
36
67
|
|
package/dist/board.js
CHANGED
package/dist/chain.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(chain_exports, {
|
|
|
29
29
|
buildAttest: () => import_csd_tx.buildAttest,
|
|
30
30
|
buildPropose: () => import_csd_tx.buildPropose,
|
|
31
31
|
buildSend: () => import_csd_tx.buildSend,
|
|
32
|
+
buildSendVerified: () => import_csd_tx.buildSendVerified,
|
|
32
33
|
canonicalJson: () => import_csd_codec.canonicalJson,
|
|
33
34
|
deserialize: () => import_csd_codec.deserialize,
|
|
34
35
|
expectedBits: () => import_csd_light.expectedBits,
|
|
@@ -60,6 +61,7 @@ __export(chain_exports, {
|
|
|
60
61
|
txid: () => import_csd_codec.txid,
|
|
61
62
|
verifyContentBytes: () => import_csd_codec.verifyContentBytes,
|
|
62
63
|
verifyDigest: () => import_csd_crypto.verifyDigest,
|
|
64
|
+
verifyInputValues: () => import_csd_client.verifyInputValues,
|
|
63
65
|
verifyMerkleProof: () => import_csd_codec.verifyMerkleProof
|
|
64
66
|
});
|
|
65
67
|
module.exports = __toCommonJS(chain_exports);
|
|
@@ -70,6 +72,8 @@ var import_csd_crypto = require("@inversealtruism/csd-crypto");
|
|
|
70
72
|
var import_csd_light = require("@inversealtruism/csd-light");
|
|
71
73
|
var import_csd_client2 = require("@inversealtruism/csd-client");
|
|
72
74
|
var import_csd_light2 = require("@inversealtruism/csd-light");
|
|
75
|
+
var import_csd_tx2 = require("@inversealtruism/csd-tx");
|
|
76
|
+
var import_csd_crypto2 = require("@inversealtruism/csd-crypto");
|
|
73
77
|
var Chain = class {
|
|
74
78
|
client;
|
|
75
79
|
opts;
|
|
@@ -90,6 +94,29 @@ var Chain = class {
|
|
|
90
94
|
submit(nodeJsonTx) {
|
|
91
95
|
return this.client.submit(nodeJsonTx);
|
|
92
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Build + sign (+ optionally broadcast) a transfer with INPUT-VALUE VERIFICATION (the H2-safe send).
|
|
99
|
+
* Fetches the signer's UTXOs, selects, then re-verifies each selected input's REAL value against the
|
|
100
|
+
* chain (`verifyInputValues` recomputes each source tx's txid, so a lying RPC can't under-report) and
|
|
101
|
+
* computes change from the VERIFIED total — closing the UTXO-VALUE-1 implicit-fee-burn. Fail-closed:
|
|
102
|
+
* if any source can't be verified it signs nothing. Prefer this over the pure `buildSend`. Pass
|
|
103
|
+
* `broadcast:false` to get the signed tx back without submitting.
|
|
104
|
+
*/
|
|
105
|
+
async send(p) {
|
|
106
|
+
const from = (0, import_csd_crypto2.addrFromPriv)(p.priv);
|
|
107
|
+
const u = await this.client.utxos(from);
|
|
108
|
+
const built = await (0, import_csd_tx2.buildSendVerified)({
|
|
109
|
+
outputs: p.outputs,
|
|
110
|
+
fee: p.fee,
|
|
111
|
+
utxos: u.utxos,
|
|
112
|
+
priv: p.priv,
|
|
113
|
+
maxFee: p.maxFee,
|
|
114
|
+
verify: (inputs) => (0, import_csd_client2.verifyInputValues)(this.client, inputs)
|
|
115
|
+
});
|
|
116
|
+
if (!built.ok || p.broadcast === false) return built;
|
|
117
|
+
const submitResult = await this.client.submit(built.nodeJson);
|
|
118
|
+
return { ...built, submitResult };
|
|
119
|
+
}
|
|
93
120
|
/** A verifying light client over the same RPC (built once, reused). */
|
|
94
121
|
light() {
|
|
95
122
|
if (!this._light) {
|
|
@@ -109,6 +136,7 @@ var Chain = class {
|
|
|
109
136
|
buildAttest,
|
|
110
137
|
buildPropose,
|
|
111
138
|
buildSend,
|
|
139
|
+
buildSendVerified,
|
|
112
140
|
canonicalJson,
|
|
113
141
|
deserialize,
|
|
114
142
|
expectedBits,
|
|
@@ -140,5 +168,6 @@ var Chain = class {
|
|
|
140
168
|
txid,
|
|
141
169
|
verifyContentBytes,
|
|
142
170
|
verifyDigest,
|
|
171
|
+
verifyInputValues,
|
|
143
172
|
verifyMerkleProof
|
|
144
173
|
});
|
package/dist/chain.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as _inversealtruism_csd_client from '@inversealtruism/csd-client';
|
|
2
2
|
import { CsdClient } from '@inversealtruism/csd-client';
|
|
3
|
-
export { ClientOptions, CsdClient, RpcBlock, RpcHeaderJson, RpcSubmit, RpcTip, RpcTxJson, RpcUtxo, RpcUtxos, rpcHeaderToHeader, rpcTxToTx } from '@inversealtruism/csd-client';
|
|
4
|
-
|
|
3
|
+
export { ClientOptions, CsdClient, RpcBlock, RpcHeaderJson, RpcSubmit, RpcTip, RpcTxJson, RpcUtxo, RpcUtxos, rpcHeaderToHeader, rpcTxToTx, verifyInputValues } from '@inversealtruism/csd-client';
|
|
4
|
+
import { BuildResult } from '@inversealtruism/csd-tx';
|
|
5
|
+
export { BuildResult, InputVerifier, Selection, Signed, Utxo, buildAttest, buildPropose, buildSend, buildSendVerified, selectInputs, signTx, txSize, txToNodeJson } from '@inversealtruism/csd-tx';
|
|
5
6
|
export { App, BlockHeader, Tx, TxInput, TxOutput, canonicalJson, deserialize, hb, headerHash, hx, merkleBranch, merkleRoot, payloadHash, serialize, sha256d, sighash, strip0x, strippedTx, txid, verifyContentBytes, verifyMerkleProof } from '@inversealtruism/csd-codec';
|
|
6
7
|
export { ADDR_RE, addrFromPriv, addrFromPub, hash160, isValidAddr, isValidPriv, keygen, pubFromPriv, randomNonce, signDigest, verifyDigest } from '@inversealtruism/csd-crypto';
|
|
7
8
|
import { LightClient } from '@inversealtruism/csd-light';
|
|
@@ -29,6 +30,26 @@ declare class Chain {
|
|
|
29
30
|
utxos(addr: string): Promise<_inversealtruism_csd_client.RpcUtxos>;
|
|
30
31
|
/** Submit a signed transaction (node-shaped JSON from `signTx().nodeJson`). */
|
|
31
32
|
submit(nodeJsonTx: unknown): Promise<_inversealtruism_csd_client.RpcSubmit>;
|
|
33
|
+
/**
|
|
34
|
+
* Build + sign (+ optionally broadcast) a transfer with INPUT-VALUE VERIFICATION (the H2-safe send).
|
|
35
|
+
* Fetches the signer's UTXOs, selects, then re-verifies each selected input's REAL value against the
|
|
36
|
+
* chain (`verifyInputValues` recomputes each source tx's txid, so a lying RPC can't under-report) and
|
|
37
|
+
* computes change from the VERIFIED total — closing the UTXO-VALUE-1 implicit-fee-burn. Fail-closed:
|
|
38
|
+
* if any source can't be verified it signs nothing. Prefer this over the pure `buildSend`. Pass
|
|
39
|
+
* `broadcast:false` to get the signed tx back without submitting.
|
|
40
|
+
*/
|
|
41
|
+
send(p: {
|
|
42
|
+
priv: string;
|
|
43
|
+
outputs: {
|
|
44
|
+
to: string;
|
|
45
|
+
value: number;
|
|
46
|
+
}[];
|
|
47
|
+
fee: number;
|
|
48
|
+
maxFee?: number;
|
|
49
|
+
broadcast?: boolean;
|
|
50
|
+
}): Promise<BuildResult & {
|
|
51
|
+
submitResult?: _inversealtruism_csd_client.RpcSubmit;
|
|
52
|
+
}>;
|
|
32
53
|
/** A verifying light client over the same RPC (built once, reused). */
|
|
33
54
|
light(): LightClient;
|
|
34
55
|
}
|
package/dist/chain.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as _inversealtruism_csd_client from '@inversealtruism/csd-client';
|
|
2
2
|
import { CsdClient } from '@inversealtruism/csd-client';
|
|
3
|
-
export { ClientOptions, CsdClient, RpcBlock, RpcHeaderJson, RpcSubmit, RpcTip, RpcTxJson, RpcUtxo, RpcUtxos, rpcHeaderToHeader, rpcTxToTx } from '@inversealtruism/csd-client';
|
|
4
|
-
|
|
3
|
+
export { ClientOptions, CsdClient, RpcBlock, RpcHeaderJson, RpcSubmit, RpcTip, RpcTxJson, RpcUtxo, RpcUtxos, rpcHeaderToHeader, rpcTxToTx, verifyInputValues } from '@inversealtruism/csd-client';
|
|
4
|
+
import { BuildResult } from '@inversealtruism/csd-tx';
|
|
5
|
+
export { BuildResult, InputVerifier, Selection, Signed, Utxo, buildAttest, buildPropose, buildSend, buildSendVerified, selectInputs, signTx, txSize, txToNodeJson } from '@inversealtruism/csd-tx';
|
|
5
6
|
export { App, BlockHeader, Tx, TxInput, TxOutput, canonicalJson, deserialize, hb, headerHash, hx, merkleBranch, merkleRoot, payloadHash, serialize, sha256d, sighash, strip0x, strippedTx, txid, verifyContentBytes, verifyMerkleProof } from '@inversealtruism/csd-codec';
|
|
6
7
|
export { ADDR_RE, addrFromPriv, addrFromPub, hash160, isValidAddr, isValidPriv, keygen, pubFromPriv, randomNonce, signDigest, verifyDigest } from '@inversealtruism/csd-crypto';
|
|
7
8
|
import { LightClient } from '@inversealtruism/csd-light';
|
|
@@ -29,6 +30,26 @@ declare class Chain {
|
|
|
29
30
|
utxos(addr: string): Promise<_inversealtruism_csd_client.RpcUtxos>;
|
|
30
31
|
/** Submit a signed transaction (node-shaped JSON from `signTx().nodeJson`). */
|
|
31
32
|
submit(nodeJsonTx: unknown): Promise<_inversealtruism_csd_client.RpcSubmit>;
|
|
33
|
+
/**
|
|
34
|
+
* Build + sign (+ optionally broadcast) a transfer with INPUT-VALUE VERIFICATION (the H2-safe send).
|
|
35
|
+
* Fetches the signer's UTXOs, selects, then re-verifies each selected input's REAL value against the
|
|
36
|
+
* chain (`verifyInputValues` recomputes each source tx's txid, so a lying RPC can't under-report) and
|
|
37
|
+
* computes change from the VERIFIED total — closing the UTXO-VALUE-1 implicit-fee-burn. Fail-closed:
|
|
38
|
+
* if any source can't be verified it signs nothing. Prefer this over the pure `buildSend`. Pass
|
|
39
|
+
* `broadcast:false` to get the signed tx back without submitting.
|
|
40
|
+
*/
|
|
41
|
+
send(p: {
|
|
42
|
+
priv: string;
|
|
43
|
+
outputs: {
|
|
44
|
+
to: string;
|
|
45
|
+
value: number;
|
|
46
|
+
}[];
|
|
47
|
+
fee: number;
|
|
48
|
+
maxFee?: number;
|
|
49
|
+
broadcast?: boolean;
|
|
50
|
+
}): Promise<BuildResult & {
|
|
51
|
+
submitResult?: _inversealtruism_csd_client.RpcSubmit;
|
|
52
|
+
}>;
|
|
32
53
|
/** A verifying light client over the same RPC (built once, reused). */
|
|
33
54
|
light(): LightClient;
|
|
34
55
|
}
|
package/dist/chain.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
buildAttest,
|
|
9
9
|
buildPropose,
|
|
10
10
|
buildSend,
|
|
11
|
+
buildSendVerified,
|
|
11
12
|
canonicalJson,
|
|
12
13
|
deserialize,
|
|
13
14
|
expectedBits,
|
|
@@ -39,8 +40,9 @@ import {
|
|
|
39
40
|
txid,
|
|
40
41
|
verifyContentBytes,
|
|
41
42
|
verifyDigest,
|
|
43
|
+
verifyInputValues,
|
|
42
44
|
verifyMerkleProof
|
|
43
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-5KAUQH72.js";
|
|
44
46
|
export {
|
|
45
47
|
ADDR_RE,
|
|
46
48
|
Chain,
|
|
@@ -51,6 +53,7 @@ export {
|
|
|
51
53
|
buildAttest,
|
|
52
54
|
buildPropose,
|
|
53
55
|
buildSend,
|
|
56
|
+
buildSendVerified,
|
|
54
57
|
canonicalJson,
|
|
55
58
|
deserialize,
|
|
56
59
|
expectedBits,
|
|
@@ -82,5 +85,6 @@ export {
|
|
|
82
85
|
txid,
|
|
83
86
|
verifyContentBytes,
|
|
84
87
|
verifyDigest,
|
|
88
|
+
verifyInputValues,
|
|
85
89
|
verifyMerkleProof
|
|
86
90
|
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
CsdClient,
|
|
4
4
|
rpcTxToTx,
|
|
5
|
-
rpcHeaderToHeader
|
|
5
|
+
rpcHeaderToHeader,
|
|
6
|
+
verifyInputValues
|
|
6
7
|
} from "@inversealtruism/csd-client";
|
|
7
8
|
import {
|
|
8
9
|
selectInputs,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
txToNodeJson,
|
|
11
12
|
signTx,
|
|
12
13
|
buildSend,
|
|
14
|
+
buildSendVerified,
|
|
13
15
|
buildPropose,
|
|
14
16
|
buildAttest
|
|
15
17
|
} from "@inversealtruism/csd-tx";
|
|
@@ -45,8 +47,10 @@ import {
|
|
|
45
47
|
ADDR_RE
|
|
46
48
|
} from "@inversealtruism/csd-crypto";
|
|
47
49
|
import { LightClient, expectedBits, expectedBitsFromWindow } from "@inversealtruism/csd-light";
|
|
48
|
-
import { CsdClient as CsdClient2 } from "@inversealtruism/csd-client";
|
|
50
|
+
import { CsdClient as CsdClient2, verifyInputValues as verifyInputValues2 } from "@inversealtruism/csd-client";
|
|
49
51
|
import { LightClient as LightClient2 } from "@inversealtruism/csd-light";
|
|
52
|
+
import { buildSendVerified as buildSendVerified2 } from "@inversealtruism/csd-tx";
|
|
53
|
+
import { addrFromPriv as addrFromPriv2 } from "@inversealtruism/csd-crypto";
|
|
50
54
|
var Chain = class {
|
|
51
55
|
client;
|
|
52
56
|
opts;
|
|
@@ -67,6 +71,29 @@ var Chain = class {
|
|
|
67
71
|
submit(nodeJsonTx) {
|
|
68
72
|
return this.client.submit(nodeJsonTx);
|
|
69
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Build + sign (+ optionally broadcast) a transfer with INPUT-VALUE VERIFICATION (the H2-safe send).
|
|
76
|
+
* Fetches the signer's UTXOs, selects, then re-verifies each selected input's REAL value against the
|
|
77
|
+
* chain (`verifyInputValues` recomputes each source tx's txid, so a lying RPC can't under-report) and
|
|
78
|
+
* computes change from the VERIFIED total — closing the UTXO-VALUE-1 implicit-fee-burn. Fail-closed:
|
|
79
|
+
* if any source can't be verified it signs nothing. Prefer this over the pure `buildSend`. Pass
|
|
80
|
+
* `broadcast:false` to get the signed tx back without submitting.
|
|
81
|
+
*/
|
|
82
|
+
async send(p) {
|
|
83
|
+
const from = addrFromPriv2(p.priv);
|
|
84
|
+
const u = await this.client.utxos(from);
|
|
85
|
+
const built = await buildSendVerified2({
|
|
86
|
+
outputs: p.outputs,
|
|
87
|
+
fee: p.fee,
|
|
88
|
+
utxos: u.utxos,
|
|
89
|
+
priv: p.priv,
|
|
90
|
+
maxFee: p.maxFee,
|
|
91
|
+
verify: (inputs) => verifyInputValues2(this.client, inputs)
|
|
92
|
+
});
|
|
93
|
+
if (!built.ok || p.broadcast === false) return built;
|
|
94
|
+
const submitResult = await this.client.submit(built.nodeJson);
|
|
95
|
+
return { ...built, submitResult };
|
|
96
|
+
}
|
|
70
97
|
/** A verifying light client over the same RPC (built once, reused). */
|
|
71
98
|
light() {
|
|
72
99
|
if (!this._light) {
|
|
@@ -81,11 +108,13 @@ export {
|
|
|
81
108
|
CsdClient,
|
|
82
109
|
rpcTxToTx,
|
|
83
110
|
rpcHeaderToHeader,
|
|
111
|
+
verifyInputValues,
|
|
84
112
|
selectInputs,
|
|
85
113
|
txSize,
|
|
86
114
|
txToNodeJson,
|
|
87
115
|
signTx,
|
|
88
116
|
buildSend,
|
|
117
|
+
buildSendVerified,
|
|
89
118
|
buildPropose,
|
|
90
119
|
buildAttest,
|
|
91
120
|
payloadHash,
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NotInstalledError,
|
|
3
|
+
UnsupportedMethodError,
|
|
4
|
+
mapProviderError
|
|
5
|
+
} from "./chunk-UMGWVOOT.js";
|
|
6
|
+
|
|
7
|
+
// src/connect.ts
|
|
8
|
+
function discoverProviders(opts = {}) {
|
|
9
|
+
const timeoutMs = opts.timeoutMs ?? 1e3;
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
if (typeof window === "undefined") {
|
|
12
|
+
resolve([]);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const found = /* @__PURE__ */ new Map();
|
|
16
|
+
const onAnnounce = (ev) => {
|
|
17
|
+
const d = ev?.detail;
|
|
18
|
+
if (d?.info?.uuid && d?.provider?.isCairn) found.set(d.info.uuid, d);
|
|
19
|
+
};
|
|
20
|
+
window.addEventListener("csd:announceProvider", onAnnounce);
|
|
21
|
+
window.dispatchEvent(new CustomEvent("csd:requestProvider"));
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
window.removeEventListener("csd:announceProvider", onAnnounce);
|
|
24
|
+
resolve([...found.values()]);
|
|
25
|
+
}, timeoutMs);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function detectProvider(opts = {}) {
|
|
29
|
+
const timeoutMs = opts.timeoutMs ?? 3e3;
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
if (typeof window === "undefined") {
|
|
32
|
+
reject(new NotInstalledError("Not running in a browser \u2014 `window.cairn` is unavailable."));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let done = false;
|
|
36
|
+
const finish = (p) => {
|
|
37
|
+
if (done) return;
|
|
38
|
+
done = true;
|
|
39
|
+
cleanup();
|
|
40
|
+
resolve(p);
|
|
41
|
+
};
|
|
42
|
+
const onInit = () => {
|
|
43
|
+
if (!done && window.cairn?.isCairn) finish(window.cairn);
|
|
44
|
+
};
|
|
45
|
+
const onAnnounce = (ev) => {
|
|
46
|
+
const p = ev?.detail?.provider;
|
|
47
|
+
if (p?.isCairn) finish(p);
|
|
48
|
+
};
|
|
49
|
+
const timer = setTimeout(() => {
|
|
50
|
+
if (done) return;
|
|
51
|
+
done = true;
|
|
52
|
+
cleanup();
|
|
53
|
+
reject(new NotInstalledError());
|
|
54
|
+
}, timeoutMs);
|
|
55
|
+
const cleanup = () => {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
window.removeEventListener("cairn#initialized", onInit);
|
|
58
|
+
window.removeEventListener("csd:announceProvider", onAnnounce);
|
|
59
|
+
};
|
|
60
|
+
window.addEventListener("cairn#initialized", onInit);
|
|
61
|
+
window.addEventListener("csd:announceProvider", onAnnounce);
|
|
62
|
+
if (window.cairn?.isCairn) {
|
|
63
|
+
finish(window.cairn);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
window.dispatchEvent(new CustomEvent("csd:requestProvider"));
|
|
67
|
+
onInit();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function isInstalled() {
|
|
71
|
+
return typeof window !== "undefined" && !!window.cairn?.isCairn;
|
|
72
|
+
}
|
|
73
|
+
function unwrap(reply) {
|
|
74
|
+
if (reply && reply.ok) return reply.result;
|
|
75
|
+
throw mapProviderError(reply?.error ?? "Unknown wallet error");
|
|
76
|
+
}
|
|
77
|
+
var WalletConnection = class {
|
|
78
|
+
provider;
|
|
79
|
+
_addr = null;
|
|
80
|
+
constructor(provider) {
|
|
81
|
+
this.provider = provider;
|
|
82
|
+
}
|
|
83
|
+
/** The wallet's version string (e.g. "0.2.x"). */
|
|
84
|
+
get version() {
|
|
85
|
+
return this.provider.version;
|
|
86
|
+
}
|
|
87
|
+
/** The connected address, if `connect()`/`getAddress()` has resolved. */
|
|
88
|
+
get address() {
|
|
89
|
+
return this._addr;
|
|
90
|
+
}
|
|
91
|
+
/** Request connection. First time per origin prompts the user; afterwards may be silent. */
|
|
92
|
+
async connect() {
|
|
93
|
+
const { addr } = unwrap(await this.provider.connect());
|
|
94
|
+
this._addr = addr;
|
|
95
|
+
return addr;
|
|
96
|
+
}
|
|
97
|
+
/** Get the connected address (does not force a new connection prompt if already consented). */
|
|
98
|
+
async getAddress() {
|
|
99
|
+
const { addr } = unwrap(await this.provider.getAddress());
|
|
100
|
+
this._addr = addr;
|
|
101
|
+
return addr;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @deprecated First-party-only legacy login (it authenticates against the wallet's own configured
|
|
105
|
+
* server and is rejected from third-party origins). Use {@link signInWithCsd} for any site.
|
|
106
|
+
* Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx).
|
|
107
|
+
*/
|
|
108
|
+
signIn() {
|
|
109
|
+
return Promise.resolve(this.provider.signIn()).then(unwrap);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Audience-bound "Sign in with CSD" (SIWC) — the secure, replay-resistant third-party login.
|
|
113
|
+
* The wallet binds the REAL requesting origin + your single-use server `nonce` into the signed
|
|
114
|
+
* message and returns the artifact; it never mints a session. VERIFY the result SERVER-SIDE with
|
|
115
|
+
* `verifySiwc` from `@inversealtruism/csd-siwc` (domain/nonce/chain/time/signature/account), then
|
|
116
|
+
* issue your OWN session. The signature is proof-of-control, NOT a bearer token. Always prompts.
|
|
117
|
+
*/
|
|
118
|
+
signInWithCsd(params) {
|
|
119
|
+
if (!params || typeof params.nonce !== "string" || !params.nonce) {
|
|
120
|
+
return Promise.reject(new UnsupportedMethodError("signInWithCsd requires a server-issued { nonce }"));
|
|
121
|
+
}
|
|
122
|
+
if (typeof this.provider.signInWithCsd !== "function") {
|
|
123
|
+
return Promise.reject(new UnsupportedMethodError("this wallet predates Sign-in-with-CSD \u2014 ask the user to update the Cairn Wallet"));
|
|
124
|
+
}
|
|
125
|
+
return Promise.resolve(this.provider.signInWithCsd(params)).then(unwrap);
|
|
126
|
+
}
|
|
127
|
+
/** True if this wallet supports audience-bound SIWC (safe to call `signInWithCsd`). */
|
|
128
|
+
get supportsSiwc() {
|
|
129
|
+
return typeof this.provider.signInWithCsd === "function";
|
|
130
|
+
}
|
|
131
|
+
/** Feature/capability detection. Returns null if the wallet predates `getCapabilities`. */
|
|
132
|
+
async getCapabilities() {
|
|
133
|
+
if (typeof this.provider.getCapabilities !== "function") return null;
|
|
134
|
+
try {
|
|
135
|
+
return await this.provider.getCapabilities();
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Atomic fill (CairnX DvP): pay + attest in ONE tx. Always prompts with clear-signing. */
|
|
141
|
+
fillOffer(params) {
|
|
142
|
+
return Promise.resolve(this.provider.fillOffer(params)).then(unwrap);
|
|
143
|
+
}
|
|
144
|
+
/** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
|
|
145
|
+
async getPermissions() {
|
|
146
|
+
if (typeof this.provider.getPermissions !== "function") return [];
|
|
147
|
+
return unwrap(await this.provider.getPermissions());
|
|
148
|
+
}
|
|
149
|
+
/** Explicitly request permission to see the address (prompts, like connect). */
|
|
150
|
+
requestPermissions() {
|
|
151
|
+
if (typeof this.provider.requestPermissions !== "function") {
|
|
152
|
+
return Promise.reject(new UnsupportedMethodError("this wallet predates requestPermissions \u2014 use connect()"));
|
|
153
|
+
}
|
|
154
|
+
return Promise.resolve(this.provider.requestPermissions()).then(unwrap);
|
|
155
|
+
}
|
|
156
|
+
/** Revoke THIS origin's own access (silent). Returns { revoked }. Safe no-op on older wallets. */
|
|
157
|
+
async revokePermissions() {
|
|
158
|
+
if (typeof this.provider.revokePermissions !== "function") return { revoked: false };
|
|
159
|
+
return unwrap(await this.provider.revokePermissions());
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Subscribe to provider events. `accountsChanged` fires with `[]` when this site loses access
|
|
163
|
+
* (wallet lock / account switch / revoke) — re-call connect() to (re)share; `disconnect` likewise.
|
|
164
|
+
* No-op if the wallet predates events. Use the SAME handler reference with off() to unsubscribe.
|
|
165
|
+
*/
|
|
166
|
+
on(event, handler) {
|
|
167
|
+
this.provider.on?.(event, handler);
|
|
168
|
+
}
|
|
169
|
+
/** Remove a previously-added event handler (must be the same reference passed to on()). */
|
|
170
|
+
off(event, handler) {
|
|
171
|
+
this.provider.removeListener?.(event, handler);
|
|
172
|
+
}
|
|
173
|
+
/** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
|
|
174
|
+
send(params) {
|
|
175
|
+
return Promise.resolve(this.provider.send(params)).then(unwrap);
|
|
176
|
+
}
|
|
177
|
+
/** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
|
|
178
|
+
propose(params) {
|
|
179
|
+
return Promise.resolve(this.provider.propose(params)).then(unwrap);
|
|
180
|
+
}
|
|
181
|
+
/** Attest/support a proposal (always prompts). */
|
|
182
|
+
attest(params) {
|
|
183
|
+
return Promise.resolve(this.provider.attest(params)).then(unwrap);
|
|
184
|
+
}
|
|
185
|
+
/** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
|
|
186
|
+
sealClaim(params) {
|
|
187
|
+
return Promise.resolve(this.provider.sealClaim(params)).then(unwrap);
|
|
188
|
+
}
|
|
189
|
+
/** Reveal a previously sealed claim by its sealing txid (always prompts). */
|
|
190
|
+
revealClaim(txid) {
|
|
191
|
+
if (typeof txid !== "string" || !txid) {
|
|
192
|
+
return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
|
|
193
|
+
}
|
|
194
|
+
return Promise.resolve(this.provider.revealClaim(txid)).then(unwrap);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
async function getWallet(opts = {}) {
|
|
198
|
+
return new WalletConnection(await detectProvider(opts));
|
|
199
|
+
}
|
|
200
|
+
async function connect(opts = {}) {
|
|
201
|
+
const wallet = await getWallet(opts);
|
|
202
|
+
const address = await wallet.connect();
|
|
203
|
+
return { wallet, address };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
discoverProviders,
|
|
208
|
+
detectProvider,
|
|
209
|
+
isInstalled,
|
|
210
|
+
WalletConnection,
|
|
211
|
+
getWallet,
|
|
212
|
+
connect
|
|
213
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var SDK_VERSION = "0.1.2";
|
|
3
|
+
var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
|
|
4
|
+
var CairnError = class extends Error {
|
|
5
|
+
/** Stable machine code for programmatic branching. */
|
|
6
|
+
code;
|
|
7
|
+
/** The human message without the appended docs/version footer. */
|
|
8
|
+
shortMessage;
|
|
9
|
+
/** Relative docs path (under the SDK docs base), if any. */
|
|
10
|
+
docsPath;
|
|
11
|
+
/** The cairn-sdk version that produced this error. */
|
|
12
|
+
version = SDK_VERSION;
|
|
13
|
+
constructor(shortMessage, opts = {}) {
|
|
14
|
+
const docs = opts.docsPath;
|
|
15
|
+
super(
|
|
16
|
+
[shortMessage, docs ? `Docs: ${DOCS_BASE}${docs}` : "", `Version: cairn-sdk@${SDK_VERSION}`].filter(Boolean).join("\n"),
|
|
17
|
+
{ cause: opts.cause }
|
|
18
|
+
);
|
|
19
|
+
this.name = new.target.name;
|
|
20
|
+
this.code = opts.code ?? "UNKNOWN";
|
|
21
|
+
this.shortMessage = shortMessage;
|
|
22
|
+
this.docsPath = docs;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
26
|
+
* returns the DEEPEST cause (the root). Mirrors viem's `BaseError.walk()`.
|
|
27
|
+
*/
|
|
28
|
+
walk(fn) {
|
|
29
|
+
let current = this;
|
|
30
|
+
let last = this;
|
|
31
|
+
while (current != null) {
|
|
32
|
+
if (fn && fn(current)) return current;
|
|
33
|
+
last = current;
|
|
34
|
+
current = current?.cause;
|
|
35
|
+
}
|
|
36
|
+
return fn ? null : last;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var NotInstalledError = class extends CairnError {
|
|
40
|
+
constructor(message = "Cairn Wallet not detected. Install it from cairn-substrate.com.") {
|
|
41
|
+
super(message, { code: "NOT_INSTALLED", docsPath: "/install" });
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var UserRejectedError = class extends CairnError {
|
|
45
|
+
constructor(message = "Request rejected by user.") {
|
|
46
|
+
super(message, { code: "USER_REJECTED", docsPath: "/errors#user-rejected" });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var WalletLockedError = class extends CairnError {
|
|
50
|
+
constructor(message = "Wallet is locked.") {
|
|
51
|
+
super(message, { code: "WALLET_LOCKED", docsPath: "/errors#wallet-locked" });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var UnsupportedMethodError = class extends CairnError {
|
|
55
|
+
constructor(message = "Method not supported by the wallet.") {
|
|
56
|
+
super(message, { code: "UNSUPPORTED_METHOD", docsPath: "/errors#unsupported-method" });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var HttpError = class extends CairnError {
|
|
60
|
+
status;
|
|
61
|
+
url;
|
|
62
|
+
body;
|
|
63
|
+
constructor(status, url, body) {
|
|
64
|
+
super(`HTTP ${status} for ${url}${body ? `: ${String(body).slice(0, 200)}` : ""}`, { code: "HTTP_ERROR" });
|
|
65
|
+
this.status = status;
|
|
66
|
+
this.url = url;
|
|
67
|
+
this.body = body;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var ContentVerificationError = class extends CairnError {
|
|
71
|
+
expected;
|
|
72
|
+
constructor(expected) {
|
|
73
|
+
super(`Content does not match its payload hash ${expected} \u2014 bytes were tampered with in transit.`, { code: "CONTENT_VERIFICATION", docsPath: "/content#verification" });
|
|
74
|
+
this.expected = expected;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
function mapProviderError(error) {
|
|
78
|
+
const e = String(error || "").toLowerCase();
|
|
79
|
+
if (e.includes("rejected by user")) return new UserRejectedError(error);
|
|
80
|
+
if (e.includes("wallet locked")) return new WalletLockedError(error);
|
|
81
|
+
if (e.includes("unsupported dapp method") || e.includes("predates") || e.includes("too old")) return new UnsupportedMethodError(error);
|
|
82
|
+
return new CairnError(error || "Unknown wallet error");
|
|
83
|
+
}
|
|
84
|
+
function errorCode(err) {
|
|
85
|
+
return err instanceof CairnError ? err.code : "UNKNOWN";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
SDK_VERSION,
|
|
90
|
+
CairnError,
|
|
91
|
+
NotInstalledError,
|
|
92
|
+
UserRejectedError,
|
|
93
|
+
WalletLockedError,
|
|
94
|
+
UnsupportedMethodError,
|
|
95
|
+
HttpError,
|
|
96
|
+
ContentVerificationError,
|
|
97
|
+
mapProviderError,
|
|
98
|
+
errorCode
|
|
99
|
+
};
|