@daski/pay 0.1.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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -0
  3. package/dist/bin/daski.d.ts +3 -0
  4. package/dist/bin/daski.d.ts.map +1 -0
  5. package/dist/bin/daski.js +187 -0
  6. package/dist/bin/daski.js.map +1 -0
  7. package/dist/cli/args.d.ts +11 -0
  8. package/dist/cli/args.d.ts.map +1 -0
  9. package/dist/cli/args.js +82 -0
  10. package/dist/cli/args.js.map +1 -0
  11. package/dist/cli/errors.d.ts +24 -0
  12. package/dist/cli/errors.d.ts.map +1 -0
  13. package/dist/cli/errors.js +23 -0
  14. package/dist/cli/errors.js.map +1 -0
  15. package/dist/cli/output.d.ts +8 -0
  16. package/dist/cli/output.d.ts.map +1 -0
  17. package/dist/cli/output.js +76 -0
  18. package/dist/cli/output.js.map +1 -0
  19. package/dist/cli/prompt.d.ts +12 -0
  20. package/dist/cli/prompt.d.ts.map +1 -0
  21. package/dist/cli/prompt.js +101 -0
  22. package/dist/cli/prompt.js.map +1 -0
  23. package/dist/cli/redact.d.ts +26 -0
  24. package/dist/cli/redact.d.ts.map +1 -0
  25. package/dist/cli/redact.js +55 -0
  26. package/dist/cli/redact.js.map +1 -0
  27. package/dist/commands/buy.d.ts +15 -0
  28. package/dist/commands/buy.d.ts.map +1 -0
  29. package/dist/commands/buy.js +276 -0
  30. package/dist/commands/buy.js.map +1 -0
  31. package/dist/commands/doctor.d.ts +29 -0
  32. package/dist/commands/doctor.d.ts.map +1 -0
  33. package/dist/commands/doctor.js +173 -0
  34. package/dist/commands/doctor.js.map +1 -0
  35. package/dist/commands/order.d.ts +18 -0
  36. package/dist/commands/order.d.ts.map +1 -0
  37. package/dist/commands/order.js +256 -0
  38. package/dist/commands/order.js.map +1 -0
  39. package/dist/commands/signPayment.d.ts +10 -0
  40. package/dist/commands/signPayment.d.ts.map +1 -0
  41. package/dist/commands/signPayment.js +162 -0
  42. package/dist/commands/signPayment.js.map +1 -0
  43. package/dist/commands/wallet.d.ts +11 -0
  44. package/dist/commands/wallet.d.ts.map +1 -0
  45. package/dist/commands/wallet.js +104 -0
  46. package/dist/commands/wallet.js.map +1 -0
  47. package/dist/config.d.ts +64 -0
  48. package/dist/config.d.ts.map +1 -0
  49. package/dist/config.js +200 -0
  50. package/dist/config.js.map +1 -0
  51. package/dist/context.d.ts +36 -0
  52. package/dist/context.d.ts.map +1 -0
  53. package/dist/context.js +54 -0
  54. package/dist/context.js.map +1 -0
  55. package/dist/gateway/balance.d.ts +16 -0
  56. package/dist/gateway/balance.d.ts.map +1 -0
  57. package/dist/gateway/balance.js +23 -0
  58. package/dist/gateway/balance.js.map +1 -0
  59. package/dist/gateway/catalog.d.ts +49 -0
  60. package/dist/gateway/catalog.d.ts.map +1 -0
  61. package/dist/gateway/catalog.js +161 -0
  62. package/dist/gateway/catalog.js.map +1 -0
  63. package/dist/gateway/client.d.ts +80 -0
  64. package/dist/gateway/client.d.ts.map +1 -0
  65. package/dist/gateway/client.js +128 -0
  66. package/dist/gateway/client.js.map +1 -0
  67. package/dist/gateway/lifecycle.d.ts +45 -0
  68. package/dist/gateway/lifecycle.d.ts.map +1 -0
  69. package/dist/gateway/lifecycle.js +96 -0
  70. package/dist/gateway/lifecycle.js.map +1 -0
  71. package/dist/gateway/purchase.d.ts +119 -0
  72. package/dist/gateway/purchase.d.ts.map +1 -0
  73. package/dist/gateway/purchase.js +250 -0
  74. package/dist/gateway/purchase.js.map +1 -0
  75. package/dist/index.d.ts +26 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +26 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/paths.d.ts +6 -0
  80. package/dist/paths.d.ts.map +1 -0
  81. package/dist/paths.js +11 -0
  82. package/dist/paths.js.map +1 -0
  83. package/dist/signers/cdp.d.ts +7 -0
  84. package/dist/signers/cdp.d.ts.map +1 -0
  85. package/dist/signers/cdp.js +42 -0
  86. package/dist/signers/cdp.js.map +1 -0
  87. package/dist/signers/circle.d.ts +53 -0
  88. package/dist/signers/circle.d.ts.map +1 -0
  89. package/dist/signers/circle.js +136 -0
  90. package/dist/signers/circle.js.map +1 -0
  91. package/dist/signers/index.d.ts +23 -0
  92. package/dist/signers/index.d.ts.map +1 -0
  93. package/dist/signers/index.js +22 -0
  94. package/dist/signers/index.js.map +1 -0
  95. package/dist/signers/local.d.ts +3 -0
  96. package/dist/signers/local.d.ts.map +1 -0
  97. package/dist/signers/local.js +24 -0
  98. package/dist/signers/local.js.map +1 -0
  99. package/dist/signers/selfTest.d.ts +46 -0
  100. package/dist/signers/selfTest.d.ts.map +1 -0
  101. package/dist/signers/selfTest.js +113 -0
  102. package/dist/signers/selfTest.js.map +1 -0
  103. package/dist/store/keystore.d.ts +19 -0
  104. package/dist/store/keystore.d.ts.map +1 -0
  105. package/dist/store/keystore.js +226 -0
  106. package/dist/store/keystore.js.map +1 -0
  107. package/dist/store/orders.d.ts +39 -0
  108. package/dist/store/orders.d.ts.map +1 -0
  109. package/dist/store/orders.js +91 -0
  110. package/dist/store/orders.js.map +1 -0
  111. package/dist/version.d.ts +2 -0
  112. package/dist/version.d.ts.map +1 -0
  113. package/dist/version.js +21 -0
  114. package/dist/version.js.map +1 -0
  115. package/package.json +59 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Circle Developer-Controlled Wallets — implemented for the EOA account type,
3
+ * wired into the conformance suite, and marked **candidate pending conformance**.
4
+ *
5
+ * The adapter is thin on purpose: resolve the wallet, refuse anything that is
6
+ * not an externally owned account, and forward typed-data signing to Circle's
7
+ * `signTypedData`. What Circle receives is exactly the `TypedDataRequest` the
8
+ * policy validator produced — domain, types, primaryType, message — as JSON.
9
+ * Nothing is added and nothing is rewritten on the way out, which is what the
10
+ * doctor self-test later checks by recovering the signer from that same data.
11
+ *
12
+ * Smart-contract accounts (SCA) are refused outright. They sign through
13
+ * ERC-1271, where the contract decides what a valid signature is, and the
14
+ * gateway verifies purchase authorizations by plain low-s ECDSA recovery of an
15
+ * EOA. No amount of adapter code turns one into the other, so the adapter says
16
+ * so instead of producing signatures the facilitator cannot verify.
17
+ *
18
+ * What is *not* yet established is that Circle's EOA signatures settle on the
19
+ * live gateway end to end — that is what `npm run conformance -- --signer
20
+ * circle` exists to answer, and until it passes `describe()` reports the
21
+ * candidate status so `doctor` can say so out loud.
22
+ *
23
+ * Credentials come from the environment only (`CIRCLE_API_KEY`,
24
+ * `CIRCLE_ENTITY_SECRET`): a flag would put them in the process list and the
25
+ * shell history. Neither is ever printed, logged, or sent to the gateway.
26
+ */
27
+ import { getAddress, isAddress } from "viem";
28
+ import { CliError } from "../cli/errors.js";
29
+ const DOC = "https://github.com/daski-io/buyer/blob/main/docs/signers.md#circle";
30
+ const SDK_PACKAGE = "@circle-fin/developer-controlled-wallets";
31
+ export async function createCircleSigner(options = {}) {
32
+ const apiKey = process.env.CIRCLE_API_KEY;
33
+ const entitySecret = process.env.CIRCLE_ENTITY_SECRET;
34
+ if (!apiKey || !entitySecret) {
35
+ throw new CliError({
36
+ code: "DASKI_CIRCLE_CREDENTIALS_UNSET",
37
+ message: "The Circle signer needs CIRCLE_API_KEY and CIRCLE_ENTITY_SECRET in the " +
38
+ "environment. They are never taken from a flag.",
39
+ remediation: `Export both from your Circle developer console, then re-run. See ${DOC}`,
40
+ });
41
+ }
42
+ const walletId = options.wallet ?? process.env.DASKI_CIRCLE_WALLET;
43
+ if (!walletId) {
44
+ throw new CliError({
45
+ code: "DASKI_CIRCLE_WALLET_UNSET",
46
+ message: "The Circle signer needs a wallet id.",
47
+ remediation: `Set DASKI_CIRCLE_WALLET, or pass --circle-wallet <id>. See ${DOC}`,
48
+ });
49
+ }
50
+ const sdk = await loadSdk(options.sdk);
51
+ const client = sdk.initiateDeveloperControlledWalletsClient({ apiKey, entitySecret });
52
+ const wallet = (await client.getWallet({ id: walletId })).data?.wallet;
53
+ if (!wallet || typeof wallet.address !== "string") {
54
+ throw new CliError({
55
+ code: "DASKI_CIRCLE_WALLET_NOT_FOUND",
56
+ message: `Circle returned no wallet for id ${walletId}.`,
57
+ remediation: `Check the id in your Circle console, or pass another --circle-wallet <id>. See ${DOC}`,
58
+ });
59
+ }
60
+ if (wallet.accountType !== "EOA") {
61
+ throw new CliError({
62
+ code: "DASKI_CIRCLE_SCA_UNSUPPORTED",
63
+ message: `Circle wallet ${walletId} is a ${wallet.accountType} account. Smart-contract ` +
64
+ "accounts sign through ERC-1271, where the contract decides what a valid " +
65
+ "signature is; the gateway verifies purchase authorizations by plain low-s " +
66
+ "ECDSA recovery of an EOA, which cannot verify them.",
67
+ remediation: "Use a Circle wallet created with accountType EOA and pass its id with " +
68
+ `--circle-wallet <id>. See ${DOC}`,
69
+ });
70
+ }
71
+ if (!isAddress(wallet.address, { strict: false })) {
72
+ throw new CliError({
73
+ code: "DASKI_CIRCLE_WALLET_NOT_EVM",
74
+ message: `Circle wallet ${walletId} is on ${wallet.blockchain ?? "an unknown blockchain"} ` +
75
+ "and has no EVM address.",
76
+ remediation: "Pass the id of an EVM wallet (for the sandbox: BASE-SEPOLIA or EVM-TESTNET). " +
77
+ `See ${DOC}`,
78
+ });
79
+ }
80
+ const address = getAddress(wallet.address);
81
+ return {
82
+ getAddress: async () => address,
83
+ signTypedData: async (payload) => {
84
+ const response = await client.signTypedData({
85
+ walletId,
86
+ data: serializeTypedData(payload),
87
+ memo: `daski: ${payload.primaryType} on eip155:${payload.domain.chainId}`,
88
+ });
89
+ const signature = response.data?.signature;
90
+ if (typeof signature !== "string") {
91
+ throw new CliError({
92
+ code: "DASKI_CIRCLE_SIGNATURE_MISSING",
93
+ message: "Circle answered the signing request without a signature.",
94
+ remediation: `Check the wallet's state in your Circle console and re-run. See ${DOC}`,
95
+ });
96
+ }
97
+ return signature;
98
+ },
99
+ describe: () => ({
100
+ provider: "circle",
101
+ accountType: "eoa",
102
+ conformance: "candidate-pending-conformance",
103
+ }),
104
+ };
105
+ }
106
+ /**
107
+ * Exactly the request the validator produced — domain, types, primaryType,
108
+ * message — as the JSON string Circle's `signTypedData` takes. `uint256`
109
+ * values arrive as bigints, which JSON cannot carry, so they go out as decimal
110
+ * strings: the same wire form the authorization itself uses. `EIP712Domain` is
111
+ * not added to `types`; it is derived from `domain`, as the validator does.
112
+ */
113
+ export function serializeTypedData(payload) {
114
+ const { domain, types, primaryType, message } = payload;
115
+ return JSON.stringify({ domain, types, primaryType, message }, (_key, value) => (typeof value === "bigint" ? value.toString() : value));
116
+ }
117
+ /**
118
+ * `@circle-fin/developer-controlled-wallets` is not a dependency of this
119
+ * package: installing it is the operator's choice, and the local signer must
120
+ * not carry its weight.
121
+ */
122
+ async function loadSdk(sdk) {
123
+ if (sdk && typeof sdk !== "function")
124
+ return sdk;
125
+ try {
126
+ return sdk ? await sdk() : (await import(SDK_PACKAGE));
127
+ }
128
+ catch {
129
+ throw new CliError({
130
+ code: "DASKI_CIRCLE_SDK_MISSING",
131
+ message: `The Circle signer requires ${SDK_PACKAGE}, which is not installed.`,
132
+ remediation: `Run: npm install ${SDK_PACKAGE} — then see ${DOC}`,
133
+ });
134
+ }
135
+ }
136
+ //# sourceMappingURL=circle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circle.js","sourceRoot":"","sources":["../../src/signers/circle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Signer selection.
3
+ *
4
+ * Three adapters behind one interface: `local` (a viem account from the key
5
+ * store, verified by the conformance suite), and `cdp` and `circle`
6
+ * (candidates pending conformance). Whatever the backend, the request it
7
+ * signs has already been through the §4 validator, and `daski doctor` puts
8
+ * every adapter through the same self-test before calling it usable.
9
+ */
10
+ import type { SignerAdapter } from "@daski/x402-scheme";
11
+ import type { SignerKind } from "../config.js";
12
+ import { createCdpSigner } from "./cdp.js";
13
+ import { createCircleSigner } from "./circle.js";
14
+ import { createLocalSigner } from "./local.js";
15
+ export interface SignerSelection {
16
+ kind: SignerKind;
17
+ profile: string;
18
+ cdpAccount?: string | undefined;
19
+ circleWallet?: string | undefined;
20
+ }
21
+ export declare function createSigner(selection: SignerSelection): Promise<SignerAdapter>;
22
+ export { createCdpSigner, createCircleSigner, createLocalSigner };
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,CAerF;AAED,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { CliError } from "../cli/errors.js";
2
+ import { createCdpSigner } from "./cdp.js";
3
+ import { createCircleSigner } from "./circle.js";
4
+ import { createLocalSigner } from "./local.js";
5
+ export async function createSigner(selection) {
6
+ switch (selection.kind) {
7
+ case "local":
8
+ return createLocalSigner(selection.profile);
9
+ case "cdp":
10
+ return createCdpSigner({ account: selection.cdpAccount });
11
+ case "circle":
12
+ return createCircleSigner({ wallet: selection.circleWallet });
13
+ default:
14
+ throw new CliError({
15
+ code: "DASKI_SIGNER_UNKNOWN",
16
+ message: `Unknown signer "${String(selection.kind)}".`,
17
+ remediation: "Set profiles.<name>.signer to one of: local, cdp, circle.",
18
+ });
19
+ }
20
+ }
21
+ export { createCdpSigner, createCircleSigner, createLocalSigner };
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signers/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAS/C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,SAA0B;IAC3D,QAAQ,SAAS,CAAC,IAAI,EAAE,CAAC;QACvB,KAAK,OAAO;YACV,OAAO,iBAAiB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,KAAK;YACR,OAAO,eAAe,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC;QAC5D,KAAK,QAAQ;YACX,OAAO,kBAAkB,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;QAChE;YACE,MAAM,IAAI,QAAQ,CAAC;gBACjB,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,mBAAmB,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI;gBACtD,WAAW,EAAE,2DAA2D;aACzE,CAAC,CAAC;IACP,CAAC;AACH,CAAC;AAED,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SignerAdapter } from "@daski/x402-scheme";
2
+ export declare function createLocalSigner(profile: string): Promise<SignerAdapter>;
3
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/signers/local.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAuC,MAAM,oBAAoB,CAAC;AAG7F,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAc/E"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The local viem-account signer: the one adapter implemented in full.
3
+ *
4
+ * The key is held in this closure and never leaves it. `describe()` reports
5
+ * where it came from so `doctor` can flag the developer environment variable.
6
+ */
7
+ import { privateKeyToAccount } from "viem/accounts";
8
+ import {} from "viem";
9
+ import { loadKey, locateKey } from "../store/keystore.js";
10
+ export async function createLocalSigner(profile) {
11
+ const location = await locateKey(profile);
12
+ const account = privateKeyToAccount(await loadKey(profile));
13
+ const description = {
14
+ provider: location?.source === "environment" ? "local (env)" : "local",
15
+ accountType: "eoa",
16
+ conformance: "verified",
17
+ };
18
+ return {
19
+ getAddress: async () => account.address,
20
+ signTypedData: (payload) => account.signTypedData(payload),
21
+ describe: () => description,
22
+ };
23
+ }
24
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/signers/local.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAA0B,MAAM,MAAM,CAAC;AAE9C,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAe;IACrD,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5D,MAAM,WAAW,GAAsB;QACrC,QAAQ,EAAE,QAAQ,EAAE,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO;QACtE,WAAW,EAAE,KAAK;QAClB,WAAW,EAAE,UAAU;KACxB,CAAC;IACF,OAAO;QACL,UAAU,EAAE,KAAK,IAAsB,EAAE,CAAC,OAAO,CAAC,OAAO;QACzD,aAAa,EAAE,CAAC,OAAyB,EAAgB,EAAE,CACzD,OAAO,CAAC,aAAa,CAAC,OAAgB,CAAC;QACzC,QAAQ,EAAE,GAAG,EAAE,CAAC,WAAW;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The doctor's signer self-test.
3
+ *
4
+ * `daski doctor` does not take a signer's word for it. Before reporting one as
5
+ * usable it has the signer sign one fixed vector and checks the result the way
6
+ * the gateway will: the address recovered from the typed data must be the
7
+ * address the adapter claims, `s` must be in the low half of the curve order,
8
+ * and the whole thing must be 65 bytes. A signer that rewrites a field, wraps
9
+ * an ERC-1271 blob, emits a malleable twin, or simply throws fails here, on a
10
+ * machine with nothing at stake, instead of at settlement.
11
+ *
12
+ * The vector has the *shape* of a purchase — the closed 6-field
13
+ * `TransferWithAuthorization` type set, so an adapter that mishandles that
14
+ * exact structure is caught — but it can never be one: the domain is
15
+ * `DaskiDoctor` with a zero verifying contract, not any token's; the value
16
+ * and recipient are zero; the validity window is closed. No contract will
17
+ * ever accept it.
18
+ */
19
+ import { type Address, type Hex } from "viem";
20
+ import { type SignerAdapter, type TypedDataRequest } from "@daski/x402-scheme";
21
+ /** The vector's nonce: a commitment to nothing but its own name. */
22
+ export declare const SELF_TEST_NONCE: Hex;
23
+ export declare const LOW_S_MAX: bigint;
24
+ /**
25
+ * The fixed vector for one signer address and chain. Everything about it is
26
+ * pinned except the payer, which must be the signer under test so that a
27
+ * matching recovered address proves the signer signed *this* message,
28
+ * unchanged.
29
+ */
30
+ export declare function selfTestVector(signerAddress: Address, chainId: number): TypedDataRequest;
31
+ export interface SignerSelfTestResult {
32
+ passed: boolean;
33
+ /** The address the signature recovers to, or null when nothing recovers. */
34
+ recovered: Address | null;
35
+ /** True when `s` is at most secp256k1n/2. */
36
+ lowS: boolean;
37
+ /** Why the test failed. Absent on a pass. */
38
+ reason?: string;
39
+ }
40
+ /**
41
+ * Signs the vector with `signer` and checks the signature the way the gateway
42
+ * will. Never throws: a signer that throws has failed the test, and the reason
43
+ * says so.
44
+ */
45
+ export declare function runSignerSelfTest(signer: SignerAdapter, chainId: number): Promise<SignerSelfTestResult>;
46
+ //# sourceMappingURL=selfTest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selfTest.d.ts","sourceRoot":"","sources":["../../src/signers/selfTest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAOL,KAAK,OAAO,EACZ,KAAK,GAAG,EACT,MAAM,MAAM,CAAC;AACd,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAI5B,oEAAoE;AACpE,eAAO,MAAM,eAAe,EAAE,GAAsD,CAAC;AAIrF,eAAO,MAAM,SAAS,QAAmB,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAmBxF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,6CAA6C;IAC7C,IAAI,EAAE,OAAO,CAAC;IACd,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,CAAC,CA+C/B"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The doctor's signer self-test.
3
+ *
4
+ * `daski doctor` does not take a signer's word for it. Before reporting one as
5
+ * usable it has the signer sign one fixed vector and checks the result the way
6
+ * the gateway will: the address recovered from the typed data must be the
7
+ * address the adapter claims, `s` must be in the low half of the curve order,
8
+ * and the whole thing must be 65 bytes. A signer that rewrites a field, wraps
9
+ * an ERC-1271 blob, emits a malleable twin, or simply throws fails here, on a
10
+ * machine with nothing at stake, instead of at settlement.
11
+ *
12
+ * The vector has the *shape* of a purchase — the closed 6-field
13
+ * `TransferWithAuthorization` type set, so an adapter that mishandles that
14
+ * exact structure is caught — but it can never be one: the domain is
15
+ * `DaskiDoctor` with a zero verifying contract, not any token's; the value
16
+ * and recipient are zero; the validity window is closed. No contract will
17
+ * ever accept it.
18
+ */
19
+ import { getAddress, hexToBigInt, keccak256, parseSignature, recoverTypedDataAddress, stringToHex, } from "viem";
20
+ import { TRANSFER_WITH_AUTHORIZATION_PRIMARY_TYPE, TRANSFER_WITH_AUTHORIZATION_TYPES, } from "@daski/x402-scheme";
21
+ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
22
+ /** The vector's nonce: a commitment to nothing but its own name. */
23
+ export const SELF_TEST_NONCE = keccak256(stringToHex("daski-doctor-self-test"));
24
+ /** The secp256k1 group order, and the largest `s` the gateway's recovery accepts. */
25
+ const SECP256K1_N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
26
+ export const LOW_S_MAX = SECP256K1_N / 2n;
27
+ /**
28
+ * The fixed vector for one signer address and chain. Everything about it is
29
+ * pinned except the payer, which must be the signer under test so that a
30
+ * matching recovered address proves the signer signed *this* message,
31
+ * unchanged.
32
+ */
33
+ export function selfTestVector(signerAddress, chainId) {
34
+ return {
35
+ domain: {
36
+ name: "DaskiDoctor",
37
+ version: "1",
38
+ chainId,
39
+ verifyingContract: ZERO_ADDRESS,
40
+ },
41
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
42
+ primaryType: TRANSFER_WITH_AUTHORIZATION_PRIMARY_TYPE,
43
+ message: {
44
+ from: signerAddress,
45
+ to: ZERO_ADDRESS,
46
+ value: 0n,
47
+ validAfter: 0n,
48
+ validBefore: 0n,
49
+ nonce: SELF_TEST_NONCE,
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * Signs the vector with `signer` and checks the signature the way the gateway
55
+ * will. Never throws: a signer that throws has failed the test, and the reason
56
+ * says so.
57
+ */
58
+ export async function runSignerSelfTest(signer, chainId) {
59
+ let claimed;
60
+ let vector;
61
+ let signature;
62
+ try {
63
+ claimed = getAddress(await signer.getAddress());
64
+ vector = selfTestVector(claimed, chainId);
65
+ signature = await signer.signTypedData(vector);
66
+ }
67
+ catch (error) {
68
+ return failed(`the signer threw: ${errorMessage(error)}`);
69
+ }
70
+ if (typeof signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(signature)) {
71
+ return failed(`malformed signature: expected 65 bytes of 0x-prefixed hex, got ${describeShape(signature)}`);
72
+ }
73
+ let recovered;
74
+ let s;
75
+ try {
76
+ recovered = await recoverTypedDataAddress({ ...vector, signature });
77
+ s = hexToBigInt(parseSignature(signature).s);
78
+ }
79
+ catch (error) {
80
+ return failed(`the signature does not recover: ${errorMessage(error)}`);
81
+ }
82
+ const lowS = s <= LOW_S_MAX;
83
+ if (recovered !== claimed) {
84
+ return {
85
+ passed: false,
86
+ recovered,
87
+ lowS,
88
+ reason: `recovered-address mismatch: the signature recovers to ${recovered} but the ` +
89
+ `signer reports ${claimed}, so either the key is not the one claimed or a ` +
90
+ "field was rewritten before signing",
91
+ };
92
+ }
93
+ if (!lowS) {
94
+ return {
95
+ passed: false,
96
+ recovered,
97
+ lowS,
98
+ reason: "high-s signature: s is above secp256k1n/2, which the gateway's low-s ECDSA " +
99
+ "recovery rejects",
100
+ };
101
+ }
102
+ return { passed: true, recovered, lowS };
103
+ }
104
+ function failed(reason) {
105
+ return { passed: false, recovered: null, lowS: false, reason };
106
+ }
107
+ function errorMessage(error) {
108
+ return error instanceof Error ? error.message : String(error);
109
+ }
110
+ function describeShape(value) {
111
+ return typeof value === "string" ? `a ${value.length}-character string` : typeof value;
112
+ }
113
+ //# sourceMappingURL=selfTest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selfTest.js","sourceRoot":"","sources":["../../src/signers/selfTest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Hex } from "viem";
2
+ export type KeySource = "keychain" | "encrypted-file" | "environment";
3
+ export interface KeyLocation {
4
+ source: KeySource;
5
+ /** Human-readable, never the key. */
6
+ description: string;
7
+ }
8
+ /** Where a key for this profile lives, without loading it. */
9
+ export declare function locateKey(profile: string): Promise<KeyLocation | undefined>;
10
+ /** Loads the private key for a profile, prompting for a passphrase if needed. */
11
+ export declare function loadKey(profile: string): Promise<Hex>;
12
+ /**
13
+ * Stores a key for a profile. Returns where it landed so the caller can tell
14
+ * the operator — the caller prints the location, never the key.
15
+ */
16
+ export declare function storeKey(profile: string, privateKey: Hex): Promise<KeyLocation>;
17
+ /** True when a key already exists for this profile. */
18
+ export declare function hasKey(profile: string): Promise<boolean>;
19
+ //# sourceMappingURL=keystore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keystore.d.ts","sourceRoot":"","sources":["../../src/store/keystore.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAehC,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAEtE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,CAAC;IAClB,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;CACrB;AAkID,8DAA8D;AAC9D,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAqBjF;AAED,iFAAiF;AACjF,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAiC3D;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAgCrF;AAED,uDAAuD;AACvD,wBAAsB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE9D"}
@@ -0,0 +1,226 @@
1
+ /**
2
+ * §5 — key storage.
3
+ *
4
+ * Order of preference: the OS keychain, then an scrypt+AES-GCM file guarded by
5
+ * a passphrase, then — for sandbox development only — an environment variable.
6
+ * Sandbox and mainnet never share a keychain entry or a file slot, so a
7
+ * profile mix-up cannot reach a mainnet key.
8
+ *
9
+ * Nothing in this file logs, prints, or returns key material except to the
10
+ * single caller that is about to construct a signer.
11
+ */
12
+ import { createCipheriv, createDecipheriv, randomBytes, scrypt as scryptCallback } from "node:crypto";
13
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { dirname } from "node:path";
15
+ import { promisify } from "node:util";
16
+ import { CliError } from "../cli/errors.js";
17
+ import { readPassphrase } from "../cli/prompt.js";
18
+ import { keystorePath } from "../paths.js";
19
+ const scrypt = promisify(scryptCallback);
20
+ const SERVICE = "io.daski.pay";
21
+ const DOC = "https://github.com/daski-io/buyer/blob/main/docs/keys.md";
22
+ /** Interactive-grade scrypt parameters. */
23
+ const SCRYPT = { N: 1 << 17, r: 8, p: 1 };
24
+ /** Keychain and file entries are namespaced by profile, never shared. */
25
+ function accountFor(profile) {
26
+ return `payer:${profile}`;
27
+ }
28
+ /** The developer escape hatch, and only for a sandbox profile. */
29
+ function environmentKey(profile) {
30
+ const raw = process.env.DASKI_PAYER_PRIVATE_KEY;
31
+ if (!raw)
32
+ return undefined;
33
+ if (profile !== "sandbox") {
34
+ throw new CliError({
35
+ code: "DASKI_ENV_KEY_REFUSED_OFF_SANDBOX",
36
+ message: `DASKI_PAYER_PRIVATE_KEY is set, but the active profile is "${profile}". ` +
37
+ "The environment-variable signer is sandbox-only.",
38
+ remediation: `Unset DASKI_PAYER_PRIVATE_KEY, and store the ${profile} key in the OS ` +
39
+ `keychain instead: run \`daski wallet create --profile ${profile}\`. See ${DOC}`,
40
+ });
41
+ }
42
+ if (!/^0x[0-9a-fA-F]{64}$/.test(raw)) {
43
+ throw new CliError({
44
+ code: "DASKI_ENV_KEY_MALFORMED",
45
+ message: "DASKI_PAYER_PRIVATE_KEY is not a 32-byte 0x-prefixed hex key.",
46
+ remediation: `Unset it, or set a valid key. See ${DOC}`,
47
+ });
48
+ }
49
+ return raw;
50
+ }
51
+ /**
52
+ * Loads `@napi-rs/keyring` if the platform has it. It is an optional
53
+ * dependency with prebuilt binaries and no install scripts, so its absence is
54
+ * normal rather than exceptional — we fall through to the file keystore.
55
+ */
56
+ async function keyring(profile) {
57
+ if (process.env.DASKI_DISABLE_KEYCHAIN === "1")
58
+ return undefined;
59
+ try {
60
+ const module = await import("@napi-rs/keyring");
61
+ const Entry = module.Entry;
62
+ return new Entry(SERVICE, accountFor(profile));
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ }
68
+ function readKeystoreFile() {
69
+ try {
70
+ const parsed = JSON.parse(readFileSync(keystorePath(), "utf8"));
71
+ if (parsed.version !== 1 || typeof parsed.entries !== "object")
72
+ throw new Error("shape");
73
+ return parsed;
74
+ }
75
+ catch {
76
+ return { version: 1, entries: {} };
77
+ }
78
+ }
79
+ function writeKeystoreFile(file) {
80
+ const path = keystorePath();
81
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
82
+ writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 });
83
+ chmodSync(path, 0o600);
84
+ }
85
+ async function encrypt(privateKey, passphrase) {
86
+ const salt = randomBytes(32);
87
+ const key = await scrypt(passphrase, salt, 32, SCRYPT);
88
+ const iv = randomBytes(12);
89
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
90
+ const ciphertext = Buffer.concat([
91
+ cipher.update(Buffer.from(privateKey.slice(2), "hex")),
92
+ cipher.final(),
93
+ ]);
94
+ return {
95
+ kdf: "scrypt",
96
+ N: SCRYPT.N, r: SCRYPT.r, p: SCRYPT.p,
97
+ salt: salt.toString("base64"),
98
+ iv: iv.toString("base64"),
99
+ tag: cipher.getAuthTag().toString("base64"),
100
+ ciphertext: ciphertext.toString("base64"),
101
+ };
102
+ }
103
+ async function decrypt(entry, passphrase) {
104
+ const key = await scrypt(passphrase, Buffer.from(entry.salt, "base64"), 32, {
105
+ N: entry.N, r: entry.r, p: entry.p,
106
+ });
107
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(entry.iv, "base64"));
108
+ decipher.setAuthTag(Buffer.from(entry.tag, "base64"));
109
+ try {
110
+ const plaintext = Buffer.concat([
111
+ decipher.update(Buffer.from(entry.ciphertext, "base64")),
112
+ decipher.final(),
113
+ ]);
114
+ return `0x${plaintext.toString("hex")}`;
115
+ }
116
+ catch {
117
+ // GCM authentication failed: a wrong passphrase, or a tampered file.
118
+ throw new CliError({
119
+ code: "DASKI_KEYSTORE_DECRYPT_FAILED",
120
+ message: "The keystore could not be decrypted with that passphrase.",
121
+ remediation: "Re-run and enter the passphrase used when the key was created. If it " +
122
+ `is lost, the key cannot be recovered from this file. See ${DOC}`,
123
+ });
124
+ }
125
+ }
126
+ /** Where a key for this profile lives, without loading it. */
127
+ export async function locateKey(profile) {
128
+ if (process.env.DASKI_PAYER_PRIVATE_KEY) {
129
+ return {
130
+ source: "environment",
131
+ description: "DASKI_PAYER_PRIVATE_KEY (developer/sandbox only)",
132
+ };
133
+ }
134
+ const entry = await keyring(profile);
135
+ if (entry) {
136
+ try {
137
+ if (entry.getPassword()) {
138
+ return { source: "keychain", description: `OS keychain (${SERVICE}/${accountFor(profile)})` };
139
+ }
140
+ }
141
+ catch {
142
+ // An unavailable keychain is not an error here; fall through to the file.
143
+ }
144
+ }
145
+ if (readKeystoreFile().entries[accountFor(profile)]) {
146
+ return { source: "encrypted-file", description: `${keystorePath()} (scrypt + AES-256-GCM)` };
147
+ }
148
+ return undefined;
149
+ }
150
+ /** Loads the private key for a profile, prompting for a passphrase if needed. */
151
+ export async function loadKey(profile) {
152
+ const fromEnvironment = environmentKey(profile);
153
+ if (fromEnvironment)
154
+ return fromEnvironment;
155
+ const entry = await keyring(profile);
156
+ if (entry) {
157
+ try {
158
+ const stored = entry.getPassword();
159
+ if (stored) {
160
+ if (!/^0x[0-9a-fA-F]{64}$/.test(stored)) {
161
+ throw new CliError({
162
+ code: "DASKI_KEYCHAIN_ENTRY_MALFORMED",
163
+ message: `The keychain entry for "${profile}" is not a valid private key.`,
164
+ remediation: `Delete it and re-run \`daski wallet create --profile ${profile}\`. See ${DOC}`,
165
+ });
166
+ }
167
+ return stored;
168
+ }
169
+ }
170
+ catch (error) {
171
+ if (error instanceof CliError)
172
+ throw error;
173
+ }
174
+ }
175
+ const stored = readKeystoreFile().entries[accountFor(profile)];
176
+ if (!stored) {
177
+ throw new CliError({
178
+ code: "DASKI_NO_KEY_FOR_PROFILE",
179
+ message: `No signing key is configured for the "${profile}" profile.`,
180
+ remediation: `Run: daski wallet create --profile ${profile}`,
181
+ });
182
+ }
183
+ const passphrase = await readPassphrase(`Passphrase for the ${profile} keystore: `);
184
+ return decrypt(stored, passphrase);
185
+ }
186
+ /**
187
+ * Stores a key for a profile. Returns where it landed so the caller can tell
188
+ * the operator — the caller prints the location, never the key.
189
+ */
190
+ export async function storeKey(profile, privateKey) {
191
+ const entry = await keyring(profile);
192
+ if (entry) {
193
+ try {
194
+ entry.setPassword(privateKey);
195
+ return { source: "keychain", description: `OS keychain (${SERVICE}/${accountFor(profile)})` };
196
+ }
197
+ catch {
198
+ // No usable keychain (headless Linux without libsecret, for example).
199
+ }
200
+ }
201
+ const passphrase = await readPassphrase(`No OS keychain is available. Choose a passphrase for ${keystorePath()}: `);
202
+ if (passphrase.length < 8) {
203
+ throw new CliError({
204
+ code: "DASKI_PASSPHRASE_TOO_SHORT",
205
+ message: "The keystore passphrase must be at least 8 characters.",
206
+ remediation: "Re-run and choose a longer passphrase.",
207
+ });
208
+ }
209
+ const again = await readPassphrase("Confirm passphrase: ");
210
+ if (again !== passphrase) {
211
+ throw new CliError({
212
+ code: "DASKI_PASSPHRASE_MISMATCH",
213
+ message: "The passphrases did not match.",
214
+ remediation: "Re-run and enter the same passphrase twice.",
215
+ });
216
+ }
217
+ const file = readKeystoreFile();
218
+ file.entries[accountFor(profile)] = await encrypt(privateKey, passphrase);
219
+ writeKeystoreFile(file);
220
+ return { source: "encrypted-file", description: `${keystorePath()} (scrypt + AES-256-GCM)` };
221
+ }
222
+ /** True when a key already exists for this profile. */
223
+ export async function hasKey(profile) {
224
+ return (await locateKey(profile)) !== undefined;
225
+ }
226
+ //# sourceMappingURL=keystore.js.map