@artblocks/abx-sdk 0.1.0-alpha.13 → 0.1.0-alpha.14
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/abi/generated.d.ts +926 -249
- package/dist/abi/generated.d.ts.map +1 -1
- package/dist/abi/generated.js +41 -39
- package/dist/abi/generated.js.map +1 -1
- package/dist/abi/index.d.ts +468 -61
- package/dist/abi/index.d.ts.map +1 -1
- package/dist/abi/index.js +16 -7
- package/dist/abi/index.js.map +1 -1
- package/dist/anchors.d.ts +70 -32
- package/dist/anchors.d.ts.map +1 -1
- package/dist/anchors.js +163 -32
- package/dist/anchors.js.map +1 -1
- package/dist/create2.d.ts +78 -7
- package/dist/create2.d.ts.map +1 -1
- package/dist/create2.js +149 -20
- package/dist/create2.js.map +1 -1
- package/dist/creator-token.d.ts +71 -4
- package/dist/creator-token.d.ts.map +1 -1
- package/dist/creator-token.js +94 -4
- package/dist/creator-token.js.map +1 -1
- package/dist/deploy.d.ts +49 -23
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +156 -76
- package/dist/deploy.js.map +1 -1
- package/dist/deployments.d.ts +4 -0
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +110 -45
- package/dist/deployments.js.map +1 -1
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +24 -0
- package/dist/errors.js.map +1 -1
- package/dist/generator-document.d.ts +3 -2
- package/dist/generator-document.d.ts.map +1 -1
- package/dist/generator-document.js +3 -2
- package/dist/generator-document.js.map +1 -1
- package/dist/generator.d.ts +43 -1
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +29 -0
- package/dist/generator.js.map +1 -1
- package/dist/inspect.d.ts +1 -1
- package/dist/inspect.js +2 -2
- package/dist/inspect.js.map +1 -1
- package/dist/onchain-uri.d.ts +11 -0
- package/dist/onchain-uri.d.ts.map +1 -1
- package/dist/onchain-uri.js +20 -0
- package/dist/onchain-uri.js.map +1 -1
- package/dist/ops.d.ts +210 -11
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +308 -34
- package/dist/ops.js.map +1 -1
- package/dist/reconstruct.d.ts +1 -0
- package/dist/reconstruct.d.ts.map +1 -1
- package/dist/reconstruct.js +13 -1
- package/dist/reconstruct.js.map +1 -1
- package/dist/service.d.ts +1 -1
- package/dist/service.js +1 -1
- package/dist/spine.d.ts +3 -4
- package/dist/spine.d.ts.map +1 -1
- package/dist/spine.js +0 -0
- package/dist/spine.js.map +1 -1
- package/dist/staging.d.ts +32 -3
- package/dist/staging.d.ts.map +1 -1
- package/dist/staging.js +85 -8
- package/dist/staging.js.map +1 -1
- package/dist/tokendata.d.ts +1 -1
- package/dist/tokendata.d.ts.map +1 -1
- package/dist/tokendata.js +21 -3
- package/dist/tokendata.js.map +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +1 -1
- package/dist/tokens.js.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/ops.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { encodeFunctionData, parseEventLogs, toHex, zeroAddress } from 'viem';
|
|
2
|
-
import { oneOfOneImageAbi, oneOfOneImageFactoryAbi,
|
|
2
|
+
import { oneOfOneImageAbi, oneOfOneImageFactoryAbi, seriesImageAbi, seriesImageFactoryAbi, abxMetadataRendererBytecode, abxFixedPriceMinterAbi, abxFixedPriceMinterBytecode, abxFixedPriceMinter1155Abi, abxFixedPriceMinter1155Bytecode, abxSeedSourceAbi, abxSeedSourceBytecode, seriesCodeAbi, seriesCodeFactoryAbi, oneOfOneEditionAbi, oneOfOneEditionFactoryAbi, editionImageFactoryAbi, editionCodeFactoryAbi, } from './abi/index.js';
|
|
3
3
|
import { encodeTag } from './spine.js';
|
|
4
|
-
import {
|
|
4
|
+
import { SeedSourceUnusableError } from './errors.js';
|
|
5
|
+
// The two edition-image factories are library-linked, so their initcode comes from the `link*`
|
|
6
|
+
// helpers, never from the raw `*Bytecode` exports (which still carry solc's `__$…$__` placeholder).
|
|
7
|
+
import { ABX_SALT, CREATE2_PROXY, create2Calldata, linkEditionFactory, linkFactory, linkOneOfOneEditionFactory, linkSeriesFactory, } from './create2.js';
|
|
5
8
|
const ZERO_VALUE = '0x0';
|
|
6
9
|
function short(addr) {
|
|
7
10
|
return addr.length > 12 ? `${addr.slice(0, 6)}…${addr.slice(-4)}` : addr;
|
|
@@ -470,12 +473,16 @@ export function prepareDeployOneOfOne(args) {
|
|
|
470
473
|
},
|
|
471
474
|
};
|
|
472
475
|
}
|
|
473
|
-
/** The unsigned form of the factory (trust-anchor) deploy —
|
|
476
|
+
/** The unsigned form of the factory (trust-anchor) deploy — CREATE2 via the keyless proxy at the
|
|
477
|
+
* canonical salt, matching the forge `Deploy` script and `predictFactory()` (create2.ts). A trust
|
|
478
|
+
* anchor is the one address platforms allowlist, so it has to be the same on every chain and
|
|
479
|
+
* computable before the first tx; a plain creation (`to: null`) would make it depend on the
|
|
480
|
+
* deployer's nonce and could never match the recorded manifest. */
|
|
474
481
|
export function prepareDeployFactory(args) {
|
|
475
482
|
return {
|
|
476
483
|
op: 'deploy-factory',
|
|
477
|
-
to:
|
|
478
|
-
data:
|
|
484
|
+
to: CREATE2_PROXY,
|
|
485
|
+
data: create2Calldata(ABX_SALT.oneOfOneFactory, linkFactory()),
|
|
479
486
|
value: ZERO_VALUE,
|
|
480
487
|
chainId: args.chainId,
|
|
481
488
|
summary: 'Deploy the canonical clone factory (trust anchor)',
|
|
@@ -610,12 +617,14 @@ export function prepareDeploySeries(args) {
|
|
|
610
617
|
},
|
|
611
618
|
};
|
|
612
619
|
}
|
|
613
|
-
/** The unsigned form of the Series factory (trust-anchor) deploy —
|
|
620
|
+
/** The unsigned form of the Series factory (trust-anchor) deploy — CREATE2 via the keyless proxy at
|
|
621
|
+
* the canonical salt, matching the forge `DeploySeries` script and `predictSeriesFactory()`. Same
|
|
622
|
+
* reasoning as `prepareDeployFactory` above. */
|
|
614
623
|
export function prepareDeploySeriesFactory(args) {
|
|
615
624
|
return {
|
|
616
625
|
op: 'deploy-series-factory',
|
|
617
|
-
to:
|
|
618
|
-
data:
|
|
626
|
+
to: CREATE2_PROXY,
|
|
627
|
+
data: create2Calldata(ABX_SALT.seriesFactory, linkSeriesFactory()),
|
|
619
628
|
value: ZERO_VALUE,
|
|
620
629
|
chainId: args.chainId,
|
|
621
630
|
summary: 'Deploy the canonical Series clone factory (trust anchor)',
|
|
@@ -625,7 +634,7 @@ export function prepareDeploySeriesFactory(args) {
|
|
|
625
634
|
// ── ERC-1155 editions (IAbxEditionMint + Edition Supply) ──────────────────────
|
|
626
635
|
// Ids are caller-named, not a sequential cursor — a minter/owner targets any id, in any order,
|
|
627
636
|
// with a per-mint amount. `mint`'s signature is uniform across all three edition kinds
|
|
628
|
-
// (`OneOfOneEdition` reverts unless `id === 0`, its id space fixed to the single
|
|
637
|
+
// (`OneOfOneEdition` reverts unless `id === 0`, its id space fixed to the single work), so
|
|
629
638
|
// every op below encodes against `oneOfOneEditionAbi` — the narrowest-common-superset ABI for
|
|
630
639
|
// the family, exactly like `oneOfOneImageAbi` plays that role on the 721 side.
|
|
631
640
|
/**
|
|
@@ -734,15 +743,21 @@ export function prepareDeployOneOfOneEdition(args) {
|
|
|
734
743
|
/** The unsigned form of the 1/1-edition factory (trust-anchor) deploy — CREATE2 via the keyless
|
|
735
744
|
* proxy + canonical salt, so it lands at the SAME (predictable) address on every chain, matching
|
|
736
745
|
* `predictOneOfOneEditionFactory()` (create2.ts). See {@link prepareDeployRenderer} for why this
|
|
737
|
-
* is `to: CREATE2_PROXY`, not a plain creation.
|
|
746
|
+
* is `to: CREATE2_PROXY`, not a plain creation.
|
|
747
|
+
*
|
|
748
|
+
* The initcode goes out LINKED against `AbxEditionLib` (`linkOneOfOneEditionFactory()`): the
|
|
749
|
+
* embedded `OneOfOneEdition` implementation delegates its uri / creator-token / edition-supply
|
|
750
|
+
* bodies into that library, so the shipped bytecode carries a solc `__$…$__` placeholder. That
|
|
751
|
+
* library must already be on-chain at `predictEditionLib()` for the deployed factory to work —
|
|
752
|
+
* {@link deployOneOfOneEditionFactory} (deploy.ts) is the path that guarantees it. */
|
|
738
753
|
export function prepareDeployOneOfOneEditionFactory(args) {
|
|
739
754
|
return {
|
|
740
755
|
op: 'deploy-one-of-one-edition-factory',
|
|
741
756
|
to: CREATE2_PROXY,
|
|
742
|
-
data: create2Calldata(ABX_SALT.oneOfOneEditionFactory,
|
|
757
|
+
data: create2Calldata(ABX_SALT.oneOfOneEditionFactory, linkOneOfOneEditionFactory()),
|
|
743
758
|
value: ZERO_VALUE,
|
|
744
759
|
chainId: args.chainId,
|
|
745
|
-
summary: 'Deploy the canonical 1/1-edition clone factory (trust anchor) — CREATE2, deterministic address',
|
|
760
|
+
summary: 'Deploy the canonical 1/1-edition clone factory (trust anchor), linked against AbxEditionLib — CREATE2, deterministic address',
|
|
746
761
|
fields: {},
|
|
747
762
|
};
|
|
748
763
|
}
|
|
@@ -772,15 +787,17 @@ export function prepareDeployEditionImage(args) {
|
|
|
772
787
|
}
|
|
773
788
|
/** The unsigned form of the edition-image factory (trust-anchor) deploy — CREATE2 via the
|
|
774
789
|
* keyless proxy + canonical salt, matching `predictEditionFactory()` (create2.ts). See {@link
|
|
775
|
-
* prepareDeployRenderer} for why this is `to: CREATE2_PROXY`, not a plain creation
|
|
790
|
+
* prepareDeployRenderer} for why this is `to: CREATE2_PROXY`, not a plain creation, and {@link
|
|
791
|
+
* prepareDeployOneOfOneEditionFactory} for why the initcode is linked against `AbxEditionLib`
|
|
792
|
+
* (`EditionImage` delegates the same three mixins' bodies into it). */
|
|
776
793
|
export function prepareDeployEditionFactory(args) {
|
|
777
794
|
return {
|
|
778
795
|
op: 'deploy-edition-factory',
|
|
779
796
|
to: CREATE2_PROXY,
|
|
780
|
-
data: create2Calldata(ABX_SALT.editionFactory,
|
|
797
|
+
data: create2Calldata(ABX_SALT.editionFactory, linkEditionFactory()),
|
|
781
798
|
value: ZERO_VALUE,
|
|
782
799
|
chainId: args.chainId,
|
|
783
|
-
summary: 'Deploy the canonical multi-
|
|
800
|
+
summary: 'Deploy the canonical multi-work edition clone factory (trust anchor), linked against AbxEditionLib — CREATE2, deterministic address',
|
|
784
801
|
fields: {},
|
|
785
802
|
};
|
|
786
803
|
}
|
|
@@ -846,25 +863,68 @@ export function prepareConfigureSale(args) {
|
|
|
846
863
|
}
|
|
847
864
|
/**
|
|
848
865
|
* Buy one token from the shared minter. `to` omitted → minted to the signer (`purchase`);
|
|
849
|
-
* `to` set → minted to that address (`purchaseTo`).
|
|
850
|
-
*
|
|
851
|
-
* the
|
|
866
|
+
* `to` set → minted to that address (`purchaseTo`). Public — no owner rights needed.
|
|
867
|
+
*
|
|
868
|
+
* `sale` is the live terms ({@link readSaleConfig}), and they are **mandatory**: the minter takes
|
|
869
|
+
* `(expectedPaymentToken, maxPrice)` and reverts `SaleTermsChanged` if the sale has moved, which is
|
|
870
|
+
* what stops a project owner from front-running a pending purchase and spending the buyer's whole
|
|
871
|
+
* ERC-20 allowance (`configure` has no timelock). The ETH to attach is derived from those terms —
|
|
872
|
+
* `price` for an ETH sale, `0` for an ERC-20 one (where the buyer must have approved the minter to
|
|
873
|
+
* pull `price` beforehand) — so a caller can't desync the payment from the guard.
|
|
874
|
+
*
|
|
875
|
+
* `maxPrice` defaults to `sale.price` (accept exactly what was read). Pass it to state a wider
|
|
876
|
+
* ceiling. It bounds the guard only; the ETH attached still comes from `sale.price`, since the minter
|
|
877
|
+
* wants exact payment. There is deliberately no "no maximum" sentinel.
|
|
878
|
+
*
|
|
879
|
+
* **A wide `maxPrice` is how a caller opts out of the protection.** The minter refuses a sentinel
|
|
880
|
+
* precisely so that a bound has to be stated, but nothing stops a caller from stating an enormous
|
|
881
|
+
* one — and on the ERC-20 lane that hands back exactly the vector the guard closes: the minter pulls
|
|
882
|
+
* the live price from the buyer's standing allowance, so a ceiling of `2^256-1` lets a re-priced sale
|
|
883
|
+
* take the whole allowance. Omitting the argument is the safe path and is what every caller in this
|
|
884
|
+
* repo does. If you widen it, widen it by an amount you would be content to lose, and never derive it
|
|
885
|
+
* from a price you read in the same breath as sending — that is the same thing as no bound at all.
|
|
886
|
+
*
|
|
887
|
+
* **On the ETH lane, `maxPrice` is a terms ASSERTION, not slippage tolerance — do not sell it as
|
|
888
|
+
* one.** The minter requires `msg.value == price` (an equality) and V1 has no refund path, so an
|
|
889
|
+
* in-flight ETH purchase reverts `WrongPayment` if the price moves in *either* direction, however
|
|
890
|
+
* wide the ceiling. Raising `maxPrice` on an ETH sale buys the buyer nothing at all. Real tolerance
|
|
891
|
+
* exists only on the **ERC-20** lane, where the minter pulls the live `price` from the buyer's
|
|
892
|
+
* allowance and any price at or below the ceiling settles. Re-pricing a live ETH sale therefore fails
|
|
893
|
+
* every buy already in flight, by design — pause first for a clean cutover.
|
|
852
894
|
*/
|
|
853
895
|
export function preparePurchase(args) {
|
|
854
|
-
const
|
|
896
|
+
const paymentToken = args.sale.paymentToken;
|
|
897
|
+
const price = BigInt(args.sale.price);
|
|
898
|
+
const isEth = paymentToken === zeroAddress;
|
|
899
|
+
const maxPrice = args.maxPrice === undefined ? price : BigInt(args.maxPrice);
|
|
900
|
+
const value = isEth ? price : 0n; // exact payment; an ERC-20 sale attaches none
|
|
855
901
|
const toRecipient = args.to && args.to !== zeroAddress;
|
|
902
|
+
const cost = isEth ? `${price} wei` : `${price} units of ${short(paymentToken)}`;
|
|
856
903
|
return {
|
|
857
904
|
op: 'purchase',
|
|
858
905
|
to: args.minter,
|
|
859
906
|
data: toRecipient
|
|
860
|
-
? encodeFunctionData({
|
|
861
|
-
|
|
907
|
+
? encodeFunctionData({
|
|
908
|
+
abi: abxFixedPriceMinterAbi,
|
|
909
|
+
functionName: 'purchaseTo',
|
|
910
|
+
args: [args.token, args.to, paymentToken, maxPrice],
|
|
911
|
+
})
|
|
912
|
+
: encodeFunctionData({
|
|
913
|
+
abi: abxFixedPriceMinterAbi,
|
|
914
|
+
functionName: 'purchase',
|
|
915
|
+
args: [args.token, paymentToken, maxPrice],
|
|
916
|
+
}),
|
|
862
917
|
value: value > 0n ? toHex(value) : ZERO_VALUE,
|
|
863
918
|
chainId: args.chainId,
|
|
864
|
-
summary: toRecipient
|
|
865
|
-
|
|
866
|
-
:
|
|
867
|
-
|
|
919
|
+
summary: toRecipient ? `Buy 1 token → ${short(args.to)} for ${cost}` : `Buy 1 token for ${cost}`,
|
|
920
|
+
fields: {
|
|
921
|
+
minter: args.minter,
|
|
922
|
+
token: args.token,
|
|
923
|
+
to: args.to ?? '(sender)',
|
|
924
|
+
value: value.toString(),
|
|
925
|
+
expectedPaymentToken: paymentToken,
|
|
926
|
+
maxPrice: maxPrice.toString(),
|
|
927
|
+
},
|
|
868
928
|
};
|
|
869
929
|
}
|
|
870
930
|
export async function readSaleConfig(client, minter, token) {
|
|
@@ -892,7 +952,7 @@ export function prepareDeployFixedPriceMinter(args) {
|
|
|
892
952
|
}
|
|
893
953
|
// ── Fixed-price EDITION minter (the Minter spine's 1155 lane) ────────────────
|
|
894
954
|
// The structural sibling of the block above, keyed one step finer: `(token, id)` instead of just
|
|
895
|
-
// `token`, since an edition project prices each
|
|
955
|
+
// `token`, since an edition project prices each work on its own terms. See
|
|
896
956
|
// `AbxFixedPriceMinter1155`'s own doc comment for the two-grant model (mint rights via
|
|
897
957
|
// `setMinter`, sale terms via `configure` — unchanged from the 721 lane, one id finer).
|
|
898
958
|
/**
|
|
@@ -930,23 +990,44 @@ export function prepareConfigureSale1155(args) {
|
|
|
930
990
|
* Buy `quantity` copies of `id` from the shared edition minter. The id/quantity-keyed twin of
|
|
931
991
|
* {@link preparePurchase}. `to` omitted → minted to the signer (`purchase`); `to` set → minted to
|
|
932
992
|
* that address (`purchaseTo`). Public — no owner rights needed.
|
|
993
|
+
*
|
|
994
|
+
* `sale` is the live terms ({@link readSaleConfig1155}) and is mandatory, same terms guard as the 721
|
|
995
|
+
* lane — except the minter bounds the **total** (`price × quantity`), which is what actually leaves
|
|
996
|
+
* the buyer's balance. `price × quantity` is computed here (once), for both the attached ETH and the
|
|
997
|
+
* bound; `maxTotalPrice` defaults to it, and passing it states a wider ceiling.
|
|
998
|
+
*
|
|
999
|
+
* The ETH-lane caveat on {@link preparePurchase} applies here identically: `msg.value` must equal the
|
|
1000
|
+
* total exactly, so `maxTotalPrice` is an assertion about the terms, **not** slippage tolerance. Only
|
|
1001
|
+
* an ERC-20 edition sale gets real tolerance.
|
|
933
1002
|
*/
|
|
934
1003
|
export function preparePurchase1155(args) {
|
|
935
1004
|
const id = BigInt(args.tokenId);
|
|
936
1005
|
const qty = BigInt(args.quantity);
|
|
937
|
-
const
|
|
1006
|
+
const paymentToken = args.sale.paymentToken;
|
|
1007
|
+
const total = BigInt(args.sale.price) * qty;
|
|
1008
|
+
const isEth = paymentToken === zeroAddress;
|
|
1009
|
+
const maxTotalPrice = args.maxTotalPrice === undefined ? total : BigInt(args.maxTotalPrice);
|
|
1010
|
+
const value = isEth ? total : 0n; // exact payment; an ERC-20 sale attaches none
|
|
938
1011
|
const toRecipient = args.to && args.to !== zeroAddress;
|
|
1012
|
+
const cost = isEth ? `${total} wei` : `${total} units of ${short(paymentToken)}`;
|
|
1013
|
+
const copies = `${qty} cop${qty === 1n ? 'y' : 'ies'} of #${id}`;
|
|
939
1014
|
return {
|
|
940
1015
|
op: 'purchase',
|
|
941
1016
|
to: args.minter,
|
|
942
1017
|
data: toRecipient
|
|
943
|
-
? encodeFunctionData({
|
|
944
|
-
|
|
1018
|
+
? encodeFunctionData({
|
|
1019
|
+
abi: abxFixedPriceMinter1155Abi,
|
|
1020
|
+
functionName: 'purchaseTo',
|
|
1021
|
+
args: [args.token, id, qty, args.to, paymentToken, maxTotalPrice],
|
|
1022
|
+
})
|
|
1023
|
+
: encodeFunctionData({
|
|
1024
|
+
abi: abxFixedPriceMinter1155Abi,
|
|
1025
|
+
functionName: 'purchase',
|
|
1026
|
+
args: [args.token, id, qty, paymentToken, maxTotalPrice],
|
|
1027
|
+
}),
|
|
945
1028
|
value: value > 0n ? toHex(value) : ZERO_VALUE,
|
|
946
1029
|
chainId: args.chainId,
|
|
947
|
-
summary: toRecipient
|
|
948
|
-
? `Buy ${qty} cop${qty === 1n ? 'y' : 'ies'} of #${id} → ${short(args.to)}${value > 0n ? ` for ${value} wei` : ''}`
|
|
949
|
-
: `Buy ${qty} cop${qty === 1n ? 'y' : 'ies'} of #${id}${value > 0n ? ` for ${value} wei` : ''}`,
|
|
1030
|
+
summary: toRecipient ? `Buy ${copies} → ${short(args.to)} for ${cost}` : `Buy ${copies} for ${cost}`,
|
|
950
1031
|
fields: {
|
|
951
1032
|
minter: args.minter,
|
|
952
1033
|
token: args.token,
|
|
@@ -954,6 +1035,8 @@ export function preparePurchase1155(args) {
|
|
|
954
1035
|
quantity: qty.toString(),
|
|
955
1036
|
to: args.to ?? '(sender)',
|
|
956
1037
|
value: value.toString(),
|
|
1038
|
+
expectedPaymentToken: paymentToken,
|
|
1039
|
+
maxTotalPrice: maxTotalPrice.toString(),
|
|
957
1040
|
},
|
|
958
1041
|
};
|
|
959
1042
|
}
|
|
@@ -995,6 +1078,118 @@ export function prepareDeploySeedSource(args) {
|
|
|
995
1078
|
fields: {},
|
|
996
1079
|
};
|
|
997
1080
|
}
|
|
1081
|
+
// ── the seed source (Seed Source extension, Register 2) ───────────────────────
|
|
1082
|
+
// Mint-time randomness is CONFIGURATION, not hard-coded token logic: the token holds one address
|
|
1083
|
+
// and calls `IAbxSeedSource.seed(tokenId, to)` on it at mint. The canonical `AbxSeedSource` is
|
|
1084
|
+
// deliberately pseudorandom (replayable after the fact, computable *during* the mint tx), so the
|
|
1085
|
+
// documented escape for anything lottery-like is to point this at a commit-reveal / VRF-backed
|
|
1086
|
+
// source of the project's own. That promise is only real if a creator can actually SET the address
|
|
1087
|
+
// — which is what the two ops below, plus the probe, exist for.
|
|
1088
|
+
/**
|
|
1089
|
+
* Set (or clear, with `0x0`) the seed source a code project draws mint seeds from. Signer must be
|
|
1090
|
+
* the owner. **Future mints only** — a seed is settled once assigned, so re-pointing never rewrites
|
|
1091
|
+
* a token that already has one.
|
|
1092
|
+
*
|
|
1093
|
+
* Pass an address that {@link probeSeedSource} says is usable. The chain does NOT validate the
|
|
1094
|
+
* source (`SeedSourceExtension.setSeedSource` stores whatever it's handed), and the failure mode of
|
|
1095
|
+
* a bad one is the worst kind: `seedSource()` reads back exactly what you set, the `SeedSourceSet`
|
|
1096
|
+
* event fires, everything looks configured — and then every mint reverts in the ABI decode of the
|
|
1097
|
+
* source's (empty or short) return. Callers should probe first and refuse before gas.
|
|
1098
|
+
*/
|
|
1099
|
+
export function prepareSetSeedSource(args) {
|
|
1100
|
+
const clearing = args.seedSource === zeroAddress;
|
|
1101
|
+
return {
|
|
1102
|
+
op: 'set-seed-source',
|
|
1103
|
+
to: args.contract,
|
|
1104
|
+
data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'setSeedSource', args: [args.seedSource] }),
|
|
1105
|
+
value: ZERO_VALUE,
|
|
1106
|
+
chainId: args.chainId,
|
|
1107
|
+
summary: clearing
|
|
1108
|
+
? 'Clear the seed source (future mints draw no seed; assigned seeds stay settled)'
|
|
1109
|
+
: `Set seed source → ${short(args.seedSource)} (future mints only)`,
|
|
1110
|
+
fields: { contract: args.contract, seedSource: args.seedSource },
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* The seed source a token currently draws from — `zeroAddress` for "no mint-time seed", and
|
|
1115
|
+
* `undefined` when the getter isn't there at all (a 1/1 or an image Series never composed the
|
|
1116
|
+
* extension). Those two are NOT the same fact and must not be collapsed: one is a code project
|
|
1117
|
+
* that opted out, the other is a token that has no such knob to set.
|
|
1118
|
+
*/
|
|
1119
|
+
export async function readSeedSource(client, contract) {
|
|
1120
|
+
return (await client
|
|
1121
|
+
.readContract({ address: contract, abi: seriesCodeAbi, functionName: 'seedSource' })
|
|
1122
|
+
.catch(() => undefined));
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Ask a candidate seed source the exact question the token will ask it, and report whether it
|
|
1126
|
+
* answers with 32 bytes.
|
|
1127
|
+
*
|
|
1128
|
+
* **`code.length > 0` is not enough**, which is the whole reason this exists. Three separate shapes
|
|
1129
|
+
* pass a has-code check, read back correctly from `seedSource()`, emit `SeedSourceSet`, and then
|
|
1130
|
+
* revert **every mint of the collection**: no code, a permissive fallback that returns empty (a
|
|
1131
|
+
* Safe, an uninitialised proxy, a 7702-delegated EOA — the same footgun the transfer-validator
|
|
1132
|
+
* probe in `CreatorToken.sol` was written for), and anything answering with fewer than 32 bytes.
|
|
1133
|
+
* A misconfigured seed source is silent until the first buyer, so the check belongs at configure
|
|
1134
|
+
* time, loudly.
|
|
1135
|
+
*
|
|
1136
|
+
* `IAbxSeedSource.seed` is **non-`view`** (sources may keep state — commit-reveal, oracle-fed), but
|
|
1137
|
+
* an `eth_call` simulates a state-changing function perfectly well and the canonical implementation
|
|
1138
|
+
* is itself `view`, so one `eth_call` probes both kinds without sending anything.
|
|
1139
|
+
*
|
|
1140
|
+
* The call is made **as the token** (`from` = `opts.as`, when given): `msg.sender` namespaces the
|
|
1141
|
+
* canonical seed and is exactly what a caller-gating custom source checks, so probing from a
|
|
1142
|
+
* random address would false-negative a legitimate source. Same reason the transfer-validator
|
|
1143
|
+
* research used `cast call --from <collection>`.
|
|
1144
|
+
*
|
|
1145
|
+
* Never throws for an on-chain reason — every failure is a verdict.
|
|
1146
|
+
*/
|
|
1147
|
+
export async function probeSeedSource(client, source, opts = {}) {
|
|
1148
|
+
let code;
|
|
1149
|
+
try {
|
|
1150
|
+
code = await client.getCode({ address: source });
|
|
1151
|
+
}
|
|
1152
|
+
catch (err) {
|
|
1153
|
+
return { verdict: 'unreachable', address: source, error: firstLine(err) };
|
|
1154
|
+
}
|
|
1155
|
+
if (!code || code === '0x')
|
|
1156
|
+
return { verdict: 'no-code', address: source };
|
|
1157
|
+
const tokenId = opts.tokenId ?? 0n;
|
|
1158
|
+
// `to` mirrors the mint: the token asks for a seed for a recipient. The canonical source ignores
|
|
1159
|
+
// it; a custom one may not, so pass a plausible non-zero address rather than 0x0 (which a source
|
|
1160
|
+
// validating its recipient would rightly refuse, turning a good source into a false negative).
|
|
1161
|
+
const to = opts.as ?? source;
|
|
1162
|
+
try {
|
|
1163
|
+
const res = await client.call({
|
|
1164
|
+
to: source,
|
|
1165
|
+
data: encodeFunctionData({ abi: abxSeedSourceAbi, functionName: 'seed', args: [tokenId, to] }),
|
|
1166
|
+
...(opts.as ? { account: opts.as } : {}),
|
|
1167
|
+
});
|
|
1168
|
+
const bytes = res.data ? (res.data.length - 2) / 2 : 0;
|
|
1169
|
+
if (bytes === 0)
|
|
1170
|
+
return { verdict: 'empty-return', address: source, returnedBytes: 0 };
|
|
1171
|
+
if (bytes < 32)
|
|
1172
|
+
return { verdict: 'short-return', address: source, returnedBytes: bytes };
|
|
1173
|
+
return { verdict: 'ok', address: source, returnedBytes: bytes };
|
|
1174
|
+
}
|
|
1175
|
+
catch (err) {
|
|
1176
|
+
return { verdict: 'reverted', address: source, error: firstLine(err) };
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
/** First line of an error message — a revert reason is readable; viem's full dump is not. */
|
|
1180
|
+
function firstLine(err) {
|
|
1181
|
+
return String(err?.message ?? err).split('\n')[0].trim();
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* {@link probeSeedSource}, as a guard: throws {@link SeedSourceUnusableError} unless the verdict is
|
|
1185
|
+
* `ok`. For an SDK caller that just wants "refuse a bad source" without composing prose; the CLI
|
|
1186
|
+
* uses the probe directly so it can name its own flags in the refusal.
|
|
1187
|
+
*/
|
|
1188
|
+
export async function assertSeedSourceUsable(client, source, opts = {}) {
|
|
1189
|
+
const probe = await probeSeedSource(client, source, opts);
|
|
1190
|
+
if (probe.verdict !== 'ok')
|
|
1191
|
+
throw new SeedSourceUnusableError(probe);
|
|
1192
|
+
}
|
|
998
1193
|
/** Deterministic deploy of a {SeriesCode} clone — the code-project factory op. */
|
|
999
1194
|
export function prepareDeploySeriesCode(args) {
|
|
1000
1195
|
return {
|
|
@@ -1129,6 +1324,23 @@ export function prepareLockDependencies(args) {
|
|
|
1129
1324
|
fields: { contract: args.contract },
|
|
1130
1325
|
};
|
|
1131
1326
|
}
|
|
1327
|
+
/** Freeze the on-chain program (script chunks) forever — `setScriptChunk`/`removeLastScriptChunk`
|
|
1328
|
+
* revert after this. Irreversible, owner-only. This is the lock that actually freezes the WORK
|
|
1329
|
+
* of a code project; `lock-field`/`lock-uri` only freeze the metadata, and `lock-dependencies` only
|
|
1330
|
+
* the library set. All of them together freeze everything the CONTRACT stores — which is not the
|
|
1331
|
+
* same as a frozen output: params have no lock and the renderer serves them, and a `Registry`
|
|
1332
|
+
* dependency resolves from the registry at read time. */
|
|
1333
|
+
export function prepareLockScript(args) {
|
|
1334
|
+
return {
|
|
1335
|
+
op: 'lock-script',
|
|
1336
|
+
to: args.contract,
|
|
1337
|
+
data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'lockScript', args: [] }),
|
|
1338
|
+
value: ZERO_VALUE,
|
|
1339
|
+
chainId: args.chainId,
|
|
1340
|
+
summary: 'Freeze the on-chain program (script chunks) — permanent',
|
|
1341
|
+
fields: { contract: args.contract },
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1132
1344
|
/** Set a CONTRACT-scope param to a literal `bytes32` (the raw owner setter — Params base).
|
|
1133
1345
|
* Only for schema-less keys (a schema'd key closes the raw path: `SchemaGoverned`). This is
|
|
1134
1346
|
* the write behind well-known contract params like `display.gateway`. The key joins the
|
|
@@ -1169,7 +1381,15 @@ export function prepareSetContractParamData(args) {
|
|
|
1169
1381
|
/** Wire the three param-lifecycle hook addresses (configure/augment/transfer) in ONE owner call.
|
|
1170
1382
|
* The contract has no per-hook setter — `setParamHooks` writes all three at once — so a caller
|
|
1171
1383
|
* changing one hook must pass the CURRENT values for the other two (read them via `paramHooks()`),
|
|
1172
|
-
* else it silently clears them. `zeroAddress` for any role means "no hook". SeriesCode
|
|
1384
|
+
* else it silently clears them. `zeroAddress` for any role means "no hook". SeriesCode/EditionCode
|
|
1385
|
+
* only.
|
|
1386
|
+
*
|
|
1387
|
+
* Note what the `transferHook` slot is: a **veto**. Its revert bubbles and fails the transfer, and
|
|
1388
|
+
* a mint is a transfer from `0x0`, so a reverting hook stops minting for the project too. Setting
|
|
1389
|
+
* one is therefore a standing power over a collector's ability to sell — disclose it, and offer
|
|
1390
|
+
* {@link prepareLockParamHooks} to a project that wants to prove it will never arm one.
|
|
1391
|
+
*
|
|
1392
|
+
* Reverts `ParamHooksLocked()` once {@link prepareLockParamHooks} has been sent. */
|
|
1173
1393
|
export function prepareSetParamHooks(args) {
|
|
1174
1394
|
return {
|
|
1175
1395
|
op: 'set-param-hooks',
|
|
@@ -1190,6 +1410,60 @@ export function prepareSetParamHooks(args) {
|
|
|
1190
1410
|
},
|
|
1191
1411
|
};
|
|
1192
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Is the param hook set frozen (`lockParamHooks()` already sent)?
|
|
1415
|
+
*
|
|
1416
|
+
* One `eth_call` to `paramHooksLocked()`. This used to simulate `setParamHooks` as the owner and
|
|
1417
|
+
* watch for a `ParamHooksLocked()` revert, on the stated grounds that no getter existed — it does
|
|
1418
|
+
* now (`IAbxConfigurableParams`), and the simulation cost two extra reads (`paramHooks` + `owner`)
|
|
1419
|
+
* to infer what one read states.
|
|
1420
|
+
*
|
|
1421
|
+
* The other honest read is the `ParamHooksFrozen` event, which is what `reconstructProject` folds
|
|
1422
|
+
* (`ProjectState.paramHooks.locked`). Prefer the fold when you already have the logs; use this when
|
|
1423
|
+
* you have an address and one RPC round trip, which is `abx state`'s situation.
|
|
1424
|
+
*
|
|
1425
|
+
* Returns `undefined` — never throws for an on-chain reason — when the answer can't be established:
|
|
1426
|
+
* not a ConfigurableParams project, or the node refused the call. A caller must render that as
|
|
1427
|
+
* "unknown", never as "unlocked".
|
|
1428
|
+
*/
|
|
1429
|
+
export async function readParamHooksLocked(client, contract) {
|
|
1430
|
+
try {
|
|
1431
|
+
return (await client.readContract({
|
|
1432
|
+
address: contract,
|
|
1433
|
+
abi: seriesCodeAbi,
|
|
1434
|
+
functionName: 'paramHooksLocked',
|
|
1435
|
+
}));
|
|
1436
|
+
}
|
|
1437
|
+
catch {
|
|
1438
|
+
return undefined;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Freeze the param-lifecycle hook set forever — `setParamHooks` reverts `ParamHooksLocked()` after
|
|
1443
|
+
* this. Irreversible, owner-only. The sibling of `lock-script`/`lock-dependencies`/`lock-uri`, and
|
|
1444
|
+
* the one aimed at a **buyer** rather than at the metadata: the `transferHook` is a veto (its revert
|
|
1445
|
+
* fails a transfer, and a mint is a transfer from `0x0`), so while the hooks are unwritten-in-stone a
|
|
1446
|
+
* project retains a standing power over whether a collector can ever sell. Sending this gives up
|
|
1447
|
+
* three abilities permanently — arming a transfer veto, arming a write-time configure veto, and
|
|
1448
|
+
* re-pointing or clearing the read-time augment hook — and it cannot be undone, re-opened, or
|
|
1449
|
+
* time-limited. A project that never wants the power sends it before the sale; a buyer reads
|
|
1450
|
+
* `paramHooks()` plus the `ParamHooksFrozen` event (`abx state`) to check.
|
|
1451
|
+
*
|
|
1452
|
+
* It does NOT freeze anything else: schemas, values, script, dependencies, and URIs all keep their
|
|
1453
|
+
* own locks, and a hook already set keeps running exactly as before — freezing the SET is not
|
|
1454
|
+
* disarming the hooks in it.
|
|
1455
|
+
*/
|
|
1456
|
+
export function prepareLockParamHooks(args) {
|
|
1457
|
+
return {
|
|
1458
|
+
op: 'lock-param-hooks',
|
|
1459
|
+
to: args.contract,
|
|
1460
|
+
data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'lockParamHooks', args: [] }),
|
|
1461
|
+
value: ZERO_VALUE,
|
|
1462
|
+
chainId: args.chainId,
|
|
1463
|
+
summary: 'Freeze the param hooks (configure/augment/transfer) — permanent',
|
|
1464
|
+
fields: { contract: args.contract },
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1193
1467
|
/** Set a schema-governed PostParam (literal scalar) — signer must satisfy the schema's auth. */
|
|
1194
1468
|
export function prepareConfigureTokenParam(args) {
|
|
1195
1469
|
return {
|
|
@@ -1284,7 +1558,7 @@ export async function readParamSchema(client, contract, key) {
|
|
|
1284
1558
|
*
|
|
1285
1559
|
* What retiring does NOT do: erase a value already stored. That value keeps being served in token
|
|
1286
1560
|
* data. It cannot be removed, deliberately — a value written under a `TokenOwner` or `Address` leg
|
|
1287
|
-
* came from a collector, and the
|
|
1561
|
+
* came from a collector, and the creator should not be able to delete someone else's contribution.
|
|
1288
1562
|
*/
|
|
1289
1563
|
export function prepareRetireParam(args) {
|
|
1290
1564
|
const lockAfter = args.at ?? Math.floor(Date.now() / 1000) - 1;
|