@artblocks/abx-sdk 0.1.0-alpha.4 → 0.1.0-alpha.41

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 (159) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +130 -0
  3. package/dist/abi/generated.d.ts +8502 -129
  4. package/dist/abi/generated.d.ts.map +1 -1
  5. package/dist/abi/generated.js +40 -22
  6. package/dist/abi/generated.js.map +1 -1
  7. package/dist/abi/index.d.ts +2645 -168
  8. package/dist/abi/index.d.ts.map +1 -1
  9. package/dist/abi/index.js +34 -7
  10. package/dist/abi/index.js.map +1 -1
  11. package/dist/anchors.d.ts +325 -0
  12. package/dist/anchors.d.ts.map +1 -0
  13. package/dist/anchors.js +740 -0
  14. package/dist/anchors.js.map +1 -0
  15. package/dist/chain-support.d.ts +28 -0
  16. package/dist/chain-support.d.ts.map +1 -0
  17. package/dist/chain-support.js +65 -0
  18. package/dist/chain-support.js.map +1 -0
  19. package/dist/chain-support.json +60 -0
  20. package/dist/chains.d.ts +24 -10
  21. package/dist/chains.d.ts.map +1 -1
  22. package/dist/chains.js +51 -17
  23. package/dist/chains.js.map +1 -1
  24. package/dist/chunks.d.ts +162 -31
  25. package/dist/chunks.d.ts.map +1 -1
  26. package/dist/chunks.js +179 -14
  27. package/dist/chunks.js.map +1 -1
  28. package/dist/clients.d.ts +23 -11
  29. package/dist/clients.d.ts.map +1 -1
  30. package/dist/clients.js +17 -18
  31. package/dist/clients.js.map +1 -1
  32. package/dist/contract-generations.d.ts +58 -0
  33. package/dist/contract-generations.d.ts.map +1 -0
  34. package/dist/contract-generations.js +109 -0
  35. package/dist/contract-generations.js.map +1 -0
  36. package/dist/contract-generations.json +48 -0
  37. package/dist/create2.d.ts +84 -1
  38. package/dist/create2.d.ts.map +1 -1
  39. package/dist/create2.js +158 -2
  40. package/dist/create2.js.map +1 -1
  41. package/dist/creator-token.d.ts +132 -0
  42. package/dist/creator-token.d.ts.map +1 -0
  43. package/dist/creator-token.js +183 -0
  44. package/dist/creator-token.js.map +1 -0
  45. package/dist/deploy.d.ts +234 -14
  46. package/dist/deploy.d.ts.map +1 -1
  47. package/dist/deploy.js +289 -111
  48. package/dist/deploy.js.map +1 -1
  49. package/dist/deployments.d.ts +38 -5
  50. package/dist/deployments.d.ts.map +1 -1
  51. package/dist/deployments.js +157 -30
  52. package/dist/deployments.js.map +1 -1
  53. package/dist/deps.d.ts +123 -6
  54. package/dist/deps.d.ts.map +1 -1
  55. package/dist/deps.js +249 -7
  56. package/dist/deps.js.map +1 -1
  57. package/dist/env.d.ts +5 -4
  58. package/dist/env.d.ts.map +1 -1
  59. package/dist/env.js +20 -5
  60. package/dist/env.js.map +1 -1
  61. package/dist/errors.d.ts +109 -0
  62. package/dist/errors.d.ts.map +1 -0
  63. package/dist/errors.js +148 -0
  64. package/dist/errors.js.map +1 -0
  65. package/dist/execute.d.ts +118 -0
  66. package/dist/execute.d.ts.map +1 -0
  67. package/dist/execute.js +159 -0
  68. package/dist/execute.js.map +1 -0
  69. package/dist/gateways.d.ts +79 -0
  70. package/dist/gateways.d.ts.map +1 -0
  71. package/dist/gateways.js +156 -0
  72. package/dist/gateways.js.map +1 -0
  73. package/dist/generator-document.d.ts +57 -0
  74. package/dist/generator-document.d.ts.map +1 -0
  75. package/dist/generator-document.js +123 -0
  76. package/dist/generator-document.js.map +1 -0
  77. package/dist/generator.d.ts +44 -2
  78. package/dist/generator.d.ts.map +1 -1
  79. package/dist/generator.js +29 -0
  80. package/dist/generator.js.map +1 -1
  81. package/dist/index.d.ts +19 -3
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +19 -3
  84. package/dist/index.js.map +1 -1
  85. package/dist/inspect.d.ts +48 -0
  86. package/dist/inspect.d.ts.map +1 -0
  87. package/dist/inspect.js +295 -0
  88. package/dist/inspect.js.map +1 -0
  89. package/dist/migrate.d.ts +41 -0
  90. package/dist/migrate.d.ts.map +1 -0
  91. package/dist/migrate.js +142 -0
  92. package/dist/migrate.js.map +1 -0
  93. package/dist/mime.d.ts +11 -0
  94. package/dist/mime.d.ts.map +1 -0
  95. package/dist/mime.js +40 -0
  96. package/dist/mime.js.map +1 -0
  97. package/dist/node.d.ts +69 -0
  98. package/dist/node.d.ts.map +1 -0
  99. package/dist/node.js +92 -0
  100. package/dist/node.js.map +1 -0
  101. package/dist/onchain-uri.d.ts +110 -0
  102. package/dist/onchain-uri.d.ts.map +1 -0
  103. package/dist/onchain-uri.js +276 -0
  104. package/dist/onchain-uri.js.map +1 -0
  105. package/dist/ops.d.ts +507 -14
  106. package/dist/ops.d.ts.map +1 -1
  107. package/dist/ops.js +850 -34
  108. package/dist/ops.js.map +1 -1
  109. package/dist/policy.d.ts +57 -0
  110. package/dist/policy.d.ts.map +1 -0
  111. package/dist/policy.js +35 -0
  112. package/dist/policy.js.map +1 -0
  113. package/dist/probe.d.ts +58 -0
  114. package/dist/probe.d.ts.map +1 -1
  115. package/dist/probe.js +190 -9
  116. package/dist/probe.js.map +1 -1
  117. package/dist/reconstruct.d.ts +141 -3
  118. package/dist/reconstruct.d.ts.map +1 -1
  119. package/dist/reconstruct.js +479 -100
  120. package/dist/reconstruct.js.map +1 -1
  121. package/dist/resume.d.ts +147 -0
  122. package/dist/resume.d.ts.map +1 -0
  123. package/dist/resume.js +146 -0
  124. package/dist/resume.js.map +1 -0
  125. package/dist/script-chunks.d.ts +105 -0
  126. package/dist/script-chunks.d.ts.map +1 -0
  127. package/dist/script-chunks.js +158 -0
  128. package/dist/script-chunks.js.map +1 -0
  129. package/dist/service.d.ts +155 -9
  130. package/dist/service.d.ts.map +1 -1
  131. package/dist/service.js +103 -11
  132. package/dist/service.js.map +1 -1
  133. package/dist/spine.d.ts +47 -4
  134. package/dist/spine.d.ts.map +1 -1
  135. package/dist/spine.js +0 -0
  136. package/dist/spine.js.map +1 -1
  137. package/dist/staging.d.ts +139 -0
  138. package/dist/staging.d.ts.map +1 -0
  139. package/dist/staging.js +139 -0
  140. package/dist/staging.js.map +1 -0
  141. package/dist/token.d.ts +11 -1
  142. package/dist/token.d.ts.map +1 -1
  143. package/dist/token.js +32 -1
  144. package/dist/token.js.map +1 -1
  145. package/dist/tokendata.d.ts +63 -3
  146. package/dist/tokendata.d.ts.map +1 -1
  147. package/dist/tokendata.js +81 -19
  148. package/dist/tokendata.js.map +1 -1
  149. package/dist/tokens.d.ts +119 -0
  150. package/dist/tokens.d.ts.map +1 -0
  151. package/dist/tokens.js +317 -0
  152. package/dist/tokens.js.map +1 -0
  153. package/dist/types.d.ts +107 -5
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/util.d.ts +74 -0
  156. package/dist/util.d.ts.map +1 -0
  157. package/dist/util.js +106 -0
  158. package/dist/util.js.map +1 -0
  159. package/package.json +15 -6
package/dist/errors.js ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The SDK's error taxonomy — deliberately minimal. A caller that wants to react to a SPECIFIC
3
+ * failure (retry a stale estimate, surface a revert's tx hash) should catch by TYPE, not by
4
+ * parsing `.message`; every SDK-thrown error a caller is expected to distinguish extends this,
5
+ * following the same `name` + typed-fields convention `AbxServiceError` already uses (service.ts).
6
+ * This module stays small on purpose — it grows only when a real caller needs a new type to catch,
7
+ * not as a speculative taxonomy of everything that could go wrong.
8
+ */
9
+ export class AbxSdkError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'AbxSdkError';
13
+ }
14
+ }
15
+ /**
16
+ * A sent transaction MINED with `status: 'reverted'`. Thrown by {@link makeHotSender} (and anything
17
+ * else driving `SendTx`) instead of letting a revert masquerade as a "confirmed" send — carries the
18
+ * tx hash so a caller can look it up on an explorer without re-parsing a message string.
19
+ */
20
+ export class TxRevertedError extends AbxSdkError {
21
+ op;
22
+ txHash;
23
+ constructor(op, txHash) {
24
+ super(`${op} reverted (${txHash})`);
25
+ this.name = 'TxRevertedError';
26
+ this.op = op;
27
+ this.txHash = txHash;
28
+ }
29
+ }
30
+ /**
31
+ * `pinGas`'s refusal: every estimate attempt came back below the transaction's PROVABLE gas floor
32
+ * (`PreparedTx.gasFloor` — EVM code deposit at exactly 200 gas/byte, and nothing else is physics).
33
+ * That is proof the node answering the estimate hasn't seen the target's deploy block yet, not that
34
+ * the floor is wrong — see `pinGas` in `execute.ts` for the full reasoning. Carries both numbers so a
35
+ * caller can decide to retry against a different RPC rather than re-parse the message.
36
+ */
37
+ export class GasEstimateBelowFloorError extends AbxSdkError {
38
+ estimate;
39
+ floor;
40
+ constructor(estimate, floor) {
41
+ super(`Refusing to send: the RPC estimated ${estimate} gas for a transaction that provably needs at least ${floor} ` +
42
+ `(storing ${floor / 200n} bytes on-chain costs 200 gas/byte in code deposit alone). An estimate below the floor means the node ` +
43
+ `is answering from stale state — usually it has not seen the contract's deploy block yet, so the call looks like it is going to ` +
44
+ `an empty account. Sending this would revert DeploymentFailed() and burn the gas. Retry in a few seconds, or use a different RPC.`);
45
+ this.name = 'GasEstimateBelowFloorError';
46
+ this.estimate = estimate;
47
+ this.floor = floor;
48
+ }
49
+ }
50
+ /**
51
+ * No signing key resolved. SINGLE env name: `ABX_DEPLOYER_PK` — thrown by {@link makeWalletClient}
52
+ * and {@link envSigningKey} (clients.ts). Names the two retired names explicitly (`SEPOLIA_FUNDED_PK`
53
+ * / `SEPOLIA_WALLET_PK`) rather than just omitting them, because a caller upgrading from an older
54
+ * alpha has one of those set in their `.env` and would otherwise see a plain "no key found" with no
55
+ * clue that the key IS there, just under a name the SDK no longer reads.
56
+ */
57
+ export class MissingSigningKeyError extends AbxSdkError {
58
+ constructor() {
59
+ super('No signing key found. Set ABX_DEPLOYER_PK in your .env. ' +
60
+ '(SEPOLIA_FUNDED_PK and SEPOLIA_WALLET_PK are no longer read — rename the key to ABX_DEPLOYER_PK.)');
61
+ this.name = 'MissingSigningKeyError';
62
+ }
63
+ }
64
+ /**
65
+ * A registry-resolved dependency's on-chain bytes are gzip'd, and no `inflate` was supplied to
66
+ * decompress them (`resolveRegistryDep`/`dependencyScriptTags`, deps.ts). SDK core carries no
67
+ * `node:zlib` — it has to stay reachable from a browser bundle (see `test/browser-bundle.test.ts`)
68
+ * — so decompression is an injected function rather than a direct call. Thrown instead of letting
69
+ * the caller either crash on `opts.inflate(...)` being undefined or silently serve a broken
70
+ * document; names the exact fix instead of a generic "cannot read properties of undefined".
71
+ */
72
+ export class InflateRequiredError extends AbxSdkError {
73
+ constructor() {
74
+ super("A registry-resolved dependency's on-chain bytes are gzip-compressed, but no `inflate` was " +
75
+ 'provided to decompress them. On a Node host, pass `nodeInflate` from `@artblocks/abx-sdk/node` ' +
76
+ "(e.g. `dependencyScriptTags(client, state, {inflate: nodeInflate})`). In a browser, pass a " +
77
+ 'DecompressionStream-based implementation — SDK core carries no `node:zlib` so it never assumes one.');
78
+ this.name = 'InflateRequiredError';
79
+ }
80
+ }
81
+ /**
82
+ * A caller asked reconstruction to stop at the `"safe"` or `"finalized"` block tag
83
+ * ({@link resolveBlockTag}, reconstruct.ts) and the RPC either rejected `eth_getBlockByNumber` for
84
+ * that tag or answered with no block number. Thrown rather than silently falling back to `latest`:
85
+ * the whole point of naming `safe`/`finalized` is the reorg-safety guarantee that tag carries, and a
86
+ * quiet substitution would hand back a normal-looking reconstruction that scanned further than the
87
+ * caller asked for — indistinguishable from success until a reorg proves it wrong. Some
88
+ * chains/providers genuinely don't serve these tags: pre-merge chains have no post-merge finality,
89
+ * and some non-archive or indexer-backed RPCs answer neither tag at all. Expected and actionable,
90
+ * not a bug — names the fallback so a caller isn't stuck.
91
+ */
92
+ export class BlockTagUnavailableError extends AbxSdkError {
93
+ tag;
94
+ constructor(tag, cause) {
95
+ super(`This RPC doesn't support the "${tag}" block tag${cause instanceof Error ? ` (${cause.message})` : ''}. ` +
96
+ `Reconstruction can't resolve a "${tag}" boundary here — use \`latest\` (the default) or an explicit ` +
97
+ `block number instead, or point at an RPC that serves "${tag}" (most L1 mainnets and their public ` +
98
+ `testnets do; some rollups and indexer-backed endpoints don't — \`abx doctor\` rates configured endpoints).`);
99
+ this.name = 'BlockTagUnavailableError';
100
+ this.tag = tag;
101
+ }
102
+ }
103
+ /**
104
+ * A trust anchor {@link AnchorKind} that `ensureFactory`/`ensureSeriesFactory`/
105
+ * `ensureSeriesCodeFactory` (anchors.ts) could not resolve to a usable address, with
106
+ * `allowBootstrap` false — the caller must either point at a working one (override / env /
107
+ * manifest) or opt in to deploying a private one. Carries structured `detail` + `address` instead
108
+ * of a formatted sentence: the SDK doesn't know a UX's flag names or chain-key strings, so it never
109
+ * composes the actual guidance text itself — that's the CLI's `refuseMissingFactory`, which catches
110
+ * this and names the env var / `--bootstrap-factory` / the chain.
111
+ */
112
+ export class AnchorUnavailableError extends AbxSdkError {
113
+ anchor;
114
+ detail;
115
+ address;
116
+ constructor(anchor, detail, address) {
117
+ super(`${anchor} trust anchor unavailable (${detail}${address ? `: ${address}` : ''})`);
118
+ this.name = 'AnchorUnavailableError';
119
+ this.anchor = anchor;
120
+ this.detail = detail;
121
+ this.address = address;
122
+ }
123
+ }
124
+ /**
125
+ * A candidate seed source that {@link probeSeedSource} found unusable — thrown by
126
+ * `assertSeedSourceUsable` (ops.ts). Carries the whole probe (verdict + what was observed) rather
127
+ * than a formatted sentence, same reason as {@link AnchorUnavailableError}: the SDK doesn't know
128
+ * whether the caller got this address from a `--seed-source` flag, an env var, or a form field, so
129
+ * it never composes the guidance itself.
130
+ *
131
+ * Worth catching by type: this is the ONE misconfiguration whose consequence is "every mint of the
132
+ * collection reverts", and it is invisible on every read surface (`seedSource()` returns exactly
133
+ * what was set; the `SeedSourceSet` event fires) — so a UX should treat it as a hard stop, not a
134
+ * warning it can proceed past.
135
+ */
136
+ export class SeedSourceUnusableError extends AbxSdkError {
137
+ verdict;
138
+ address;
139
+ probe;
140
+ constructor(probe) {
141
+ super(`seed source ${probe.address} is not usable (${probe.verdict}${probe.error ? `: ${probe.error}` : ''})`);
142
+ this.name = 'SeedSourceUnusableError';
143
+ this.verdict = probe.verdict;
144
+ this.address = probe.address;
145
+ this.probe = probe;
146
+ }
147
+ }
148
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,eAAgB,SAAQ,WAAW;IACrC,EAAE,CAAS;IACX,MAAM,CAAM;IACrB,YAAY,EAAU,EAAE,MAAW;QACjC,KAAK,CAAC,GAAG,EAAE,cAAc,MAAM,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,0BAA2B,SAAQ,WAAW;IAChD,QAAQ,CAAS;IACjB,KAAK,CAAS;IACvB,YAAY,QAAgB,EAAE,KAAa;QACzC,KAAK,CACH,uCAAuC,QAAQ,uDAAuD,KAAK,GAAG;YAC5G,YAAY,KAAK,GAAG,IAAI,wGAAwG;YAChI,iIAAiI;YACjI,kIAAkI,CACrI,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,sBAAuB,SAAQ,WAAW;IACrD;QACE,KAAK,CACH,0DAA0D;YACxD,mGAAmG,CACtG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,oBAAqB,SAAQ,WAAW;IACnD;QACE,KAAK,CACH,4FAA4F;YAC1F,iGAAiG;YACjG,6FAA6F;YAC7F,qGAAqG,CACxG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,wBAAyB,SAAQ,WAAW;IAC9C,GAAG,CAAuB;IACnC,YAAY,GAAyB,EAAE,KAAe;QACpD,KAAK,CACH,iCAAiC,GAAG,cAAc,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI;YACvG,mCAAmC,GAAG,gEAAgE;YACtG,yDAAyD,GAAG,uCAAuC;YACnG,4GAA4G,CAC/G,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAeD;;;;;;;;GAQG;AACH,MAAM,OAAO,sBAAuB,SAAQ,WAAW;IAC5C,MAAM,CAAa;IACnB,MAAM,CAAiD;IACvD,OAAO,CAAW;IAC3B,YAAY,MAAkB,EAAE,MAAsD,EAAE,OAAiB;QACvG,KAAK,CAAC,GAAG,MAAM,8BAA8B,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACxF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,uBAAwB,SAAQ,WAAW;IAC7C,OAAO,CAAoB;IAC3B,OAAO,CAAU;IACjB,KAAK,CAAkB;IAChC,YAAY,KAAsB;QAChC,KAAK,CAAC,eAAe,KAAK,CAAC,OAAO,mBAAmB,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC/G,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Gas limits for a sequenced transaction — decided HERE, once, rather than re-read at signing time.
3
+ *
4
+ * The failure this exists to prevent, in full, because it is not obvious and it cost a reporter every
5
+ * `deploy-code` attempt of a session: a project deploys in two transactions, and the second targets
6
+ * the contract the first just created. If the node answering `eth_estimateGas` has not yet seen the
7
+ * deploy block, the target looks like an account with no code — and an estimate for a call to a
8
+ * codeless account is just the calldata cost, around 200k. That number is then sent as the gas limit.
9
+ * Once the contract does exist the same call needs ~941k, because storing a program on-chain is
10
+ * dominated by CREATE code deposit at ~200 gas per byte. The CREATE receives 63/64 of a budget that
11
+ * cannot cover the deposit, returns 0, and Solady's `SSTORE2.write` reverts `DeploymentFailed()` — a
12
+ * revert that reads like a contract bug and is really an out-of-gas.
13
+ *
14
+ * (Measured, from the real transactions: `gasLimit 201,616 / gasUsed 198,870` and
15
+ * `gasLimit 169,301 / gasUsed 166,810` — 98.6% and 98.5% of their limits. Replaying those payloads
16
+ * against a codeless address reproduces both limits to the gas; against the real contract the same
17
+ * calls estimate at 941,331.)
18
+ *
19
+ * The deploy loop already pinned the *nonce* against exactly this read-after-write lag on a
20
+ * distributed RPC. The rule generalizes, and it is the reason this module exists: **anything read at
21
+ * send time on a distributed RPC needs pinning, not just the nonce.**
22
+ */
23
+ import type { Account, Address, Hex, PublicClient, TransactionReceipt, WalletClient } from 'viem';
24
+ import type { PreparedTx } from './ops.js';
25
+ /**
26
+ * Wait until `address` has code from THIS client's point of view. A deploy receipt proves the
27
+ * contract exists on chain; it does not prove the node answering the next request has caught up.
28
+ * Bounded — on timeout we proceed and let the gas floor carry it, since a slow RPC is not a reason
29
+ * to refuse to continue a deploy that already spent money.
30
+ */
31
+ export declare function waitForCodeAt(client: PublicClient, address: Address, timeoutMs?: number): Promise<boolean>;
32
+ /**
33
+ * Decide a transaction's gas limit once, here.
34
+ *
35
+ * The important design point, because the obvious approach is wrong: when an estimate looks too low
36
+ * we do **not** substitute a computed number. A caller can only compute the *provable* part of a
37
+ * payload's cost (see `PreparedTx.gasFloor` — code deposit is 200 gas/byte and nothing else is
38
+ * physics); the same setup multicall also carries schema writes, dependency legs, URI legs and
39
+ * mints, whose cost we cannot derive without simulating them. A "probably enough" constant is a
40
+ * number tuned to whatever case was in front of its author: it papers over the symptom, then
41
+ * under-funds the next payload that carries a few more legs, producing the identical
42
+ * `DeploymentFailed()` with a fresh mystery attached.
43
+ *
44
+ * So the floor is used as a **detector**. An estimate below a provable minimum is not "low", it is
45
+ * *impossible* — proof that the node answering us is looking at the wrong state (typically it has
46
+ * not seen the deploy block yet, so the target reads as an account with no code and the estimate
47
+ * comes back as the calldata cost alone). The right response to a broken measurement is to take it
48
+ * again, and if it stays broken, to refuse — sending a transaction we can prove is under-funded
49
+ * would burn the gas AND orphan the contract.
50
+ *
51
+ * When the estimate IS plausible it is trusted, plus headroom for state drift between estimate and
52
+ * inclusion.
53
+ */
54
+ export declare function pinGas(client: PublicClient, tx: {
55
+ from: Address;
56
+ to: Address | null;
57
+ data: Hex;
58
+ value?: Hex;
59
+ gasFloor?: Hex;
60
+ }, opts?: {
61
+ attempts?: number;
62
+ delayMs?: number;
63
+ }): Promise<bigint>;
64
+ /** Sign + broadcast one {@link PreparedTx} and return its mined receipt (for event parsing). The
65
+ * SDK's one send-injection point: a hot key ({@link makeHotSender}), a browser wallet (a
66
+ * `WalletSession`), or anything else a caller wires up — the SDK never picks or performs the
67
+ * signing itself. */
68
+ export type SendTx = (tx: PreparedTx) => Promise<TransactionReceipt>;
69
+ /** Progress from a {@link SendTx} built by {@link makeHotSender}, so a caller can narrate without
70
+ * the SDK printing anything itself (the SDK never prints — see clients.ts / chunks.ts for the same
71
+ * `onEvent` pattern). */
72
+ export type SendEvent = {
73
+ kind: 'sending';
74
+ tx: PreparedTx;
75
+ } | {
76
+ kind: 'mined';
77
+ receipt: TransactionReceipt;
78
+ tx: PreparedTx;
79
+ };
80
+ /**
81
+ * Build a {@link SendTx} that signs with an env-style hot key and broadcasts — the generalization of
82
+ * what used to be hand-rolled per call site (a deploy-then-setup loop in the CLI, the hot lane in
83
+ * `signer.ts`, the chunk-staging sender in `ownerops.ts`): one function that gets the read-after-
84
+ * write-lag handling right ONCE.
85
+ *
86
+ * Two lags, pinned once at construction / tracked locally per send, rather than re-read at send time:
87
+ * - the NONCE — read once here as `max(pending, latest)`, then incremented locally per tx. Right
88
+ * after a tx is mined a distributed RPC (e.g. Alchemy, `sepolia.base.org`) can briefly serve a
89
+ * stale `pending` transaction count (read-after-write lag), so re-fetching it for the NEXT send
90
+ * in the same sequence risks reusing the just-spent nonce — the tx it already has in the mempool
91
+ * rejects the duplicate as "replacement transaction underpriced", and that send is silently
92
+ * lost. The `max` covers the cross-INVOCATION case the local increment cannot see: a node whose
93
+ * pending view has fallen behind its own head answers the first send of the next `abx` command
94
+ * with a nonce it has already spent.
95
+ * - GAS, one field over: a tx after the first in a sequence may target a contract an EARLIER tx in
96
+ * this same sequence just created. If the node answering `eth_estimateGas` hasn't seen that
97
+ * block yet, the target looks codeless and the estimate comes back as the calldata cost alone —
98
+ * see `pinGas` above for the full story (and the measured numbers). So every send after the
99
+ * first waits for its target's code to be visible before estimating.
100
+ *
101
+ * Every send: wait for code at the target when it's not the first send in this sender's sequence
102
+ * (mirrors the exact rule the deploy-code hot lane hand-rolled: every tx after the first, since the
103
+ * first is what creates what the rest target), pin the gas (a provable floor detector, never a
104
+ * substitute limit), broadcast, wait for the receipt, and throw a typed {@link TxRevertedError} —
105
+ * carrying the tx hash — on a `reverted` status rather than reporting a burned transaction as
106
+ * "confirmed".
107
+ */
108
+ export declare function makeHotSender(args: {
109
+ wallet: WalletClient;
110
+ account: Account;
111
+ publicClient: PublicClient;
112
+ onEvent?: (e: SendEvent) => void;
113
+ }): SendTx;
114
+ /** Send a list of prepared txs, in order, via `send`; returns their receipts in the same order.
115
+ * A small convenience for the common "just run this sequence" case — narration (if any) rides
116
+ * `send`'s own `onEvent`, not a callback here. */
117
+ export declare function runPrepared(txs: PreparedTx[], send: SendTx): Promise<TransactionReceipt[]>;
118
+ //# sourceMappingURL=execute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAC,MAAM,MAAM,CAAC;AAChG,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,UAAU,CAAC;AAGzC;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAQhH;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,MAAM,CAC1B,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,EAAE,GAAG,CAAA;CAAC,EAC/E,IAAI,GAAE;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAM,GAC/C,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAED;;;sBAGsB;AACtB,MAAM,MAAM,MAAM,GAAG,CAAC,EAAE,EAAE,UAAU,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAErE;;0BAE0B;AAC1B,MAAM,MAAM,SAAS,GACjB;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAC,GACjC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAC,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;CAClC,GAAG,MAAM,CAgDT;AAED;;mDAEmD;AACnD,wBAAsB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAIhG"}
@@ -0,0 +1,159 @@
1
+ import { GasEstimateBelowFloorError, TxRevertedError } from './errors.js';
2
+ /**
3
+ * Wait until `address` has code from THIS client's point of view. A deploy receipt proves the
4
+ * contract exists on chain; it does not prove the node answering the next request has caught up.
5
+ * Bounded — on timeout we proceed and let the gas floor carry it, since a slow RPC is not a reason
6
+ * to refuse to continue a deploy that already spent money.
7
+ */
8
+ export async function waitForCodeAt(client, address, timeoutMs = 15_000) {
9
+ const deadline = Date.now() + timeoutMs;
10
+ for (;;) {
11
+ const code = await client.getCode({ address }).catch(() => undefined);
12
+ if (code && code !== '0x')
13
+ return true;
14
+ if (Date.now() >= deadline)
15
+ return false;
16
+ await new Promise((r) => setTimeout(r, 750));
17
+ }
18
+ }
19
+ /**
20
+ * Decide a transaction's gas limit once, here.
21
+ *
22
+ * The important design point, because the obvious approach is wrong: when an estimate looks too low
23
+ * we do **not** substitute a computed number. A caller can only compute the *provable* part of a
24
+ * payload's cost (see `PreparedTx.gasFloor` — code deposit is 200 gas/byte and nothing else is
25
+ * physics); the same setup multicall also carries schema writes, dependency legs, URI legs and
26
+ * mints, whose cost we cannot derive without simulating them. A "probably enough" constant is a
27
+ * number tuned to whatever case was in front of its author: it papers over the symptom, then
28
+ * under-funds the next payload that carries a few more legs, producing the identical
29
+ * `DeploymentFailed()` with a fresh mystery attached.
30
+ *
31
+ * So the floor is used as a **detector**. An estimate below a provable minimum is not "low", it is
32
+ * *impossible* — proof that the node answering us is looking at the wrong state (typically it has
33
+ * not seen the deploy block yet, so the target reads as an account with no code and the estimate
34
+ * comes back as the calldata cost alone). The right response to a broken measurement is to take it
35
+ * again, and if it stays broken, to refuse — sending a transaction we can prove is under-funded
36
+ * would burn the gas AND orphan the contract.
37
+ *
38
+ * When the estimate IS plausible it is trusted, plus headroom for state drift between estimate and
39
+ * inclusion.
40
+ */
41
+ export async function pinGas(client, tx, opts = {}) {
42
+ const floor = tx.gasFloor ? BigInt(tx.gasFloor) : 0n;
43
+ const attempts = opts.attempts ?? 3;
44
+ let lastEstimate = null;
45
+ let lastError;
46
+ for (let i = 0; i < attempts; i++) {
47
+ try {
48
+ const estimate = await client.estimateGas({
49
+ account: tx.from,
50
+ to: tx.to ?? undefined,
51
+ data: tx.data,
52
+ ...(tx.value && tx.value !== '0x0' ? { value: BigInt(tx.value) } : {}),
53
+ });
54
+ // Plausible (or nothing provable to check it against) → trust it.
55
+ if (estimate >= floor)
56
+ return (estimate * 125n) / 100n;
57
+ lastEstimate = estimate;
58
+ }
59
+ catch (err) {
60
+ lastError = err;
61
+ // A revert during estimation is a real answer about the transaction, not a lagging node —
62
+ // surface it immediately, since its message ("caller is not the owner") is the useful part.
63
+ if (floor === 0n)
64
+ throw err;
65
+ }
66
+ if (i < attempts - 1)
67
+ await new Promise((r) => setTimeout(r, opts.delayMs ?? 1_500));
68
+ }
69
+ if (lastEstimate !== null) {
70
+ throw new GasEstimateBelowFloorError(lastEstimate, floor);
71
+ }
72
+ throw lastError instanceof Error ? lastError : new Error('gas estimation failed');
73
+ }
74
+ /**
75
+ * Build a {@link SendTx} that signs with an env-style hot key and broadcasts — the generalization of
76
+ * what used to be hand-rolled per call site (a deploy-then-setup loop in the CLI, the hot lane in
77
+ * `signer.ts`, the chunk-staging sender in `ownerops.ts`): one function that gets the read-after-
78
+ * write-lag handling right ONCE.
79
+ *
80
+ * Two lags, pinned once at construction / tracked locally per send, rather than re-read at send time:
81
+ * - the NONCE — read once here as `max(pending, latest)`, then incremented locally per tx. Right
82
+ * after a tx is mined a distributed RPC (e.g. Alchemy, `sepolia.base.org`) can briefly serve a
83
+ * stale `pending` transaction count (read-after-write lag), so re-fetching it for the NEXT send
84
+ * in the same sequence risks reusing the just-spent nonce — the tx it already has in the mempool
85
+ * rejects the duplicate as "replacement transaction underpriced", and that send is silently
86
+ * lost. The `max` covers the cross-INVOCATION case the local increment cannot see: a node whose
87
+ * pending view has fallen behind its own head answers the first send of the next `abx` command
88
+ * with a nonce it has already spent.
89
+ * - GAS, one field over: a tx after the first in a sequence may target a contract an EARLIER tx in
90
+ * this same sequence just created. If the node answering `eth_estimateGas` hasn't seen that
91
+ * block yet, the target looks codeless and the estimate comes back as the calldata cost alone —
92
+ * see `pinGas` above for the full story (and the measured numbers). So every send after the
93
+ * first waits for its target's code to be visible before estimating.
94
+ *
95
+ * Every send: wait for code at the target when it's not the first send in this sender's sequence
96
+ * (mirrors the exact rule the deploy-code hot lane hand-rolled: every tx after the first, since the
97
+ * first is what creates what the rest target), pin the gas (a provable floor detector, never a
98
+ * substitute limit), broadcast, wait for the receipt, and throw a typed {@link TxRevertedError} —
99
+ * carrying the tx hash — on a `reverted` status rather than reporting a burned transaction as
100
+ * "confirmed".
101
+ */
102
+ export function makeHotSender(args) {
103
+ const { wallet, account, publicClient } = args;
104
+ const notify = args.onEvent ?? (() => { });
105
+ let nonce;
106
+ let sent = 0;
107
+ return async (tx) => {
108
+ if (nonce === undefined) {
109
+ // Fetch once, then increment locally per send — see the read-after-write-lag reasoning above.
110
+ //
111
+ // Read BOTH views and take the higher. `pending` is by definition >= `latest` on a coherent
112
+ // node, but a distributed endpoint that has not caught up with its own head serves a *lower*
113
+ // pending count than its own latest-block count — measured on `sepolia.base.org` right after
114
+ // a confirmed write, and never on the publicnode fallback. That is the cross-invocation half
115
+ // of the same lag this sender already handles within one sequence: each `abx` command is a
116
+ // fresh process, so the first send of the NEXT command re-reads a nonce the node has already
117
+ // spent, and the broadcast is rejected as a duplicate — silently, because a viem simulation
118
+ // failure means nothing is ever sent. `max` is correct-or-better in every case: with real
119
+ // pending txs `pending` wins, and against a stale view `latest` floors it.
120
+ const [pending, latest] = await Promise.all([
121
+ publicClient.getTransactionCount({ address: account.address, blockTag: 'pending' }),
122
+ publicClient.getTransactionCount({ address: account.address, blockTag: 'latest' }),
123
+ ]);
124
+ nonce = Math.max(pending, latest);
125
+ }
126
+ // The SAME lag, one field over: every send after the first in this sender's sequence may target
127
+ // what an earlier send just created.
128
+ if (sent > 0 && tx.to)
129
+ await waitForCodeAt(publicClient, tx.to);
130
+ const gas = await pinGas(publicClient, { from: account.address, to: tx.to, data: tx.data, value: tx.value, gasFloor: tx.gasFloor });
131
+ notify({ kind: 'sending', tx });
132
+ const hash = await wallet.sendTransaction({
133
+ to: tx.to ?? undefined,
134
+ data: tx.data,
135
+ value: tx.value && tx.value !== '0x0' ? BigInt(tx.value) : undefined,
136
+ account,
137
+ chain: wallet.chain,
138
+ nonce,
139
+ gas,
140
+ });
141
+ nonce += 1;
142
+ sent += 1;
143
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
144
+ if (receipt.status !== 'success')
145
+ throw new TxRevertedError(tx.op, hash);
146
+ notify({ kind: 'mined', receipt, tx });
147
+ return receipt;
148
+ };
149
+ }
150
+ /** Send a list of prepared txs, in order, via `send`; returns their receipts in the same order.
151
+ * A small convenience for the common "just run this sequence" case — narration (if any) rides
152
+ * `send`'s own `onEvent`, not a callback here. */
153
+ export async function runPrepared(txs, send) {
154
+ const receipts = [];
155
+ for (const tx of txs)
156
+ receipts.push(await send(tx));
157
+ return receipts;
158
+ }
159
+ //# sourceMappingURL=execute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.js","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import type { ProjectState } from './types.js';
2
+ /** The locator's network, which decides which gateways can be asked about it. `http` locators
3
+ * carry their own base (they are already an absolute URL) and resolve to `''` here — a caller
4
+ * returns them verbatim before consulting a gateway. */
5
+ export type LocatorNetwork = 'arweave' | 'ipfs' | 'http';
6
+ /** Resolved gateway overrides for the two content-addressed networks — the shape both
7
+ * {@link resolveGatewayBase} and {@link gatewayConfigFromEnv} speak. */
8
+ export interface GatewayOverrides {
9
+ ipfs?: string;
10
+ arweave?: string;
11
+ }
12
+ /**
13
+ * The gateway BASE for a locator network, given already-resolved `overrides` — PURE, no env
14
+ * read. Defaults to the generic public gateways (`ipfs.io` / `arweave.net`) when `overrides`
15
+ * doesn't name one; any other `network` (e.g. `http`, which carries its own base) resolves to
16
+ * `''`. Callers that want override → env → default precedence compose this with
17
+ * {@link gatewayConfigFromEnv} themselves (see `@artblocks/abx-storage`'s `resolveGatewayBase`
18
+ * for the reference compose) — kept separate so a host with its own config source never has this
19
+ * function reach into `process.env` on its behalf.
20
+ */
21
+ export declare function resolveGatewayBase(network: 'ipfs' | 'arweave' | string, overrides?: GatewayOverrides): string;
22
+ /**
23
+ * The ONLY env-reading piece of this module: `ABX_IPFS_GATEWAY` / `ABX_ARWEAVE_GATEWAY`, read via
24
+ * {@link readEnv} (safe where `process` doesn't exist). Exported separately from
25
+ * {@link resolveGatewayBase} so a host with its own gateway configuration (a database row, a
26
+ * remote config service) never has to touch `process.env` through this package at all — it just
27
+ * never calls this function and passes its own overrides straight to the pure resolver instead.
28
+ */
29
+ export declare function gatewayConfigFromEnv(): GatewayOverrides;
30
+ /** Build the URL that asks `gateway` for `id`. A locator that is already an absolute URL is asked
31
+ * verbatim — rewriting someone's URL would answer a question they didn't ask. */
32
+ export declare function gatewayUrlFor(network: LocatorNetwork, id: string, gateway: string): string;
33
+ /** A scheme whose locator is content-addressed, and therefore its own integrity anchor. */
34
+ export type ContentAddressedNetwork = 'ipfs' | 'arweave';
35
+ /**
36
+ * Normalize a gateway HOST (the `ABX_IPFS_GATEWAY` / `ABX_ARWEAVE_GATEWAY` shape, and what
37
+ * {@link resolveGatewayBase} returns) into the on-chain PREFIX shape — trailing path included, so
38
+ * projection is a raw concat. A value that already ends in the scheme's path segment is left
39
+ * alone, so an operator may configure either form.
40
+ */
41
+ export declare function gatewayPrefixFrom(network: ContentAddressedNetwork, base: string): string;
42
+ /**
43
+ * The gateway prefix this project prefers for a scheme: the collection's reserved
44
+ * `abx_gateway_*` field, else the host's env/override default, else the public floor.
45
+ *
46
+ * The env read is a FLOOR, never an override. When a project has stated a preference on chain,
47
+ * every conforming resolver must serve that one — otherwise the same token renders differently
48
+ * depending on who is serving it, which is the property `abx_provenance` exists to make checkable.
49
+ * When a project has stated nothing, a host with its own gateway is strictly better than
50
+ * `ipfs.io`, and a managed provider serving a token it does not own has no other way to say so.
51
+ */
52
+ export declare function projectGatewayPrefix(state: Pick<ProjectState, 'collectionFields'>, network: ContentAddressedNetwork, overrides?: GatewayOverrides): string;
53
+ /**
54
+ * Project a content-addressed field value into its https URL, byte-identically to the deployed
55
+ * renderer. `null` when the value locates nothing (`ipfs://` with no CID) — the caller omits an
56
+ * optional field or falls back on a required one, rather than emitting a bare prefix that 404s.
57
+ *
58
+ * Order is load-bearing and mirrors the Solidity exactly: strip the scheme, THEN substitute
59
+ * `{id}`, THEN check for an absolute URL. That last check is what stops a value which already
60
+ * names its own host — precisely what a `backend.locator()` return looks like — from coming back
61
+ * as `https://arweave.net/https://arweave.net/<txid>`.
62
+ */
63
+ export declare function projectGatewayUrl(network: ContentAddressedNetwork, value: string, prefix: string, tokenId?: string): string | null;
64
+ /**
65
+ * The inverse of {@link gatewayUrlFor} / {@link projectGatewayUrl}: recover the bare content id
66
+ * (CID or txid, plus any path suffix) from whatever form a locator arrived in.
67
+ *
68
+ * This exists because a storage backend's `locator()` returns a **gateway HTTPS URL** — the form an
69
+ * operator can click — while an `ipfs` / `arweave` metadata field must hold IDENTITY, with the
70
+ * gateway supplied at read time. Baking the operator's gateway host into the field is exactly the
71
+ * weld this projection was built to remove: it makes a CID unmigratable and makes the chain report
72
+ * `source: url` for bytes that live on IPFS.
73
+ *
74
+ * `null` when no id can be recovered — notably a subdomain-style gateway
75
+ * (`https://<cid>.ipfs.dweb.link`), which ABX's own backends never produce. A caller that gets
76
+ * `null` should keep the URL as a plain `url` field and SAY so, rather than guess.
77
+ */
78
+ export declare function contentIdFromLocator(network: ContentAddressedNetwork, locator: string): string | null;
79
+ //# sourceMappingURL=gateways.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateways.d.ts","sourceRoot":"","sources":["../src/gateways.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAgB,YAAY,EAAC,MAAM,YAAY,CAAC;AAE5D;;yDAEyD;AACzD,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzD;yEACyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,SAAS,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAI7G;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,gBAAgB,CAOvD;AAED;kFACkF;AAClF,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAK1F;AAaD,2FAA2F;AAC3F,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAAC;AAIzD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKxF;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,kBAAkB,CAAC,EAC7C,OAAO,EAAE,uBAAuB,EAChC,SAAS,CAAC,EAAE,gBAAgB,GAC3B,MAAM,CAgBR;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CAUf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgBrG"}