@zoralabs/coins-sdk 0.6.0 → 0.7.1

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/index.js CHANGED
@@ -4,133 +4,12 @@ import {
4
4
  coinFactoryABI as zoraFactoryImplABI
5
5
  } from "@zoralabs/protocol-deployments";
6
6
  import {
7
- parseEventLogs,
8
- isAddressEqual
7
+ decodeFunctionData,
8
+ isAddressEqual,
9
+ parseEventLogs
9
10
  } from "viem";
10
11
  import { base as base3 } from "viem/chains";
11
12
 
12
- // src/utils/validateClientNetwork.ts
13
- import { base, baseSepolia } from "viem/chains";
14
- var validateClientNetwork = (publicClient) => {
15
- const clientChainId = publicClient?.chain?.id;
16
- if (clientChainId === base.id) {
17
- return;
18
- }
19
- if (clientChainId === baseSepolia.id) {
20
- return;
21
- }
22
- throw new Error(
23
- "Client network needs to be base or baseSepolia for current coin deployments."
24
- );
25
- };
26
-
27
- // src/metadata/cleanAndValidateMetadataURI.ts
28
- function cleanAndValidateMetadataURI(uri) {
29
- if (uri.startsWith("ipfs://")) {
30
- return uri.replace(
31
- "ipfs://",
32
- "https://magic.decentralized-content.com/ipfs/"
33
- );
34
- }
35
- if (uri.startsWith("ar://")) {
36
- return uri.replace("ar://", "http://arweave.net/");
37
- }
38
- if (uri.startsWith("data:")) {
39
- return uri;
40
- }
41
- if (uri.startsWith("http://") || uri.startsWith("https://")) {
42
- return uri;
43
- }
44
- throw new Error("Invalid metadata URI");
45
- }
46
-
47
- // src/metadata/validateMetadataJSON.ts
48
- function validateURIString(uri) {
49
- if (typeof uri !== "string") {
50
- throw new Error("URI must be a string");
51
- }
52
- if (uri.startsWith("ipfs://")) {
53
- return true;
54
- }
55
- if (uri.startsWith("ar://")) {
56
- return true;
57
- }
58
- if (uri.startsWith("https://")) {
59
- return true;
60
- }
61
- if (uri.startsWith("data:")) {
62
- return true;
63
- }
64
- return false;
65
- }
66
- function validateMetadataJSON(metadata) {
67
- if (typeof metadata !== "object" || !metadata) {
68
- throw new Error("Metadata must be an object and exist");
69
- }
70
- if (typeof metadata.name !== "string") {
71
- throw new Error("Metadata name is required and must be a string");
72
- }
73
- if (typeof metadata.description !== "string") {
74
- throw new Error("Metadata description is required and must be a string");
75
- }
76
- if (typeof metadata.image === "string") {
77
- if (!validateURIString(metadata.image)) {
78
- throw new Error("Metadata image is not a valid URI");
79
- }
80
- } else {
81
- throw new Error("Metadata image is required and must be a string");
82
- }
83
- if ("animation_url" in metadata) {
84
- if (typeof metadata.animation_url !== "string") {
85
- throw new Error("Metadata animation_url, if provided, must be a string");
86
- }
87
- if (!validateURIString(metadata.animation_url)) {
88
- throw new Error("Metadata animation_url is not a valid URI");
89
- }
90
- }
91
- const content = "content" in metadata && metadata.content;
92
- if (content) {
93
- if (typeof content.uri !== "string") {
94
- throw new Error("If provided, content.uri must be a string");
95
- }
96
- if (!validateURIString(content.uri)) {
97
- throw new Error("If provided, content.uri must be a valid URI string");
98
- }
99
- if (typeof content.mime !== "string") {
100
- throw new Error("If provided, content.mime must be a string");
101
- }
102
- }
103
- return true;
104
- }
105
-
106
- // src/metadata/validateMetadataURIContent.ts
107
- async function validateMetadataURIContent(metadataURI) {
108
- const cleanedURI = cleanAndValidateMetadataURI(metadataURI);
109
- const response = await fetch(cleanedURI);
110
- if (!response.ok) {
111
- throw new Error("Metadata fetch failed");
112
- }
113
- if (!["application/json", "text/plain"].includes(
114
- response.headers.get("content-type") ?? ""
115
- )) {
116
- throw new Error("Metadata is not a valid JSON or plain text response type");
117
- }
118
- const metadataJson = await response.json();
119
- return validateMetadataJSON(metadataJson);
120
- }
121
-
122
- // src/utils/getChainFromId.ts
123
- import { base as base2, baseSepolia as baseSepolia2 } from "viem/chains";
124
- function getChainFromId(chainId) {
125
- if (chainId === base2.id) {
126
- return base2;
127
- }
128
- if (chainId === baseSepolia2.id) {
129
- return baseSepolia2;
130
- }
131
- throw new Error(`Chain ID ${chainId} not supported`);
132
- }
133
-
134
13
  // src/client/client.gen.ts
135
14
  import {
136
15
  createClient,
@@ -643,6 +522,160 @@ var getProfileBySocialHandle2 = async (query, options) => {
643
522
  });
644
523
  };
645
524
 
525
+ // src/metadata/cleanAndValidateMetadataURI.ts
526
+ function cleanAndValidateMetadataURI(uri) {
527
+ if (uri.startsWith("ipfs://")) {
528
+ return uri.replace(
529
+ "ipfs://",
530
+ "https://magic.decentralized-content.com/ipfs/"
531
+ );
532
+ }
533
+ if (uri.startsWith("ar://")) {
534
+ return uri.replace("ar://", "http://arweave.net/");
535
+ }
536
+ if (uri.startsWith("data:")) {
537
+ return uri;
538
+ }
539
+ if (uri.startsWith("http://") || uri.startsWith("https://")) {
540
+ return uri;
541
+ }
542
+ throw new Error("Invalid metadata URI");
543
+ }
544
+
545
+ // src/metadata/validateMetadataJSON.ts
546
+ function validateURIString(uri) {
547
+ if (typeof uri !== "string") {
548
+ throw new Error("URI must be a string");
549
+ }
550
+ if (uri.startsWith("ipfs://")) {
551
+ return true;
552
+ }
553
+ if (uri.startsWith("ar://")) {
554
+ return true;
555
+ }
556
+ if (uri.startsWith("https://")) {
557
+ return true;
558
+ }
559
+ if (uri.startsWith("data:")) {
560
+ return true;
561
+ }
562
+ return false;
563
+ }
564
+ function validateMetadataJSON(metadata) {
565
+ if (typeof metadata !== "object" || !metadata) {
566
+ throw new Error("Metadata must be an object and exist");
567
+ }
568
+ if (typeof metadata.name !== "string") {
569
+ throw new Error("Metadata name is required and must be a string");
570
+ }
571
+ if (typeof metadata.description !== "string") {
572
+ throw new Error("Metadata description is required and must be a string");
573
+ }
574
+ if (typeof metadata.image === "string") {
575
+ if (!validateURIString(metadata.image)) {
576
+ throw new Error("Metadata image is not a valid URI");
577
+ }
578
+ } else {
579
+ throw new Error("Metadata image is required and must be a string");
580
+ }
581
+ if ("animation_url" in metadata) {
582
+ if (typeof metadata.animation_url !== "string") {
583
+ throw new Error("Metadata animation_url, if provided, must be a string");
584
+ }
585
+ if (!validateURIString(metadata.animation_url)) {
586
+ throw new Error("Metadata animation_url is not a valid URI");
587
+ }
588
+ }
589
+ const content = "content" in metadata && metadata.content;
590
+ if (content) {
591
+ if (typeof content.uri !== "string") {
592
+ throw new Error("If provided, content.uri must be a string");
593
+ }
594
+ if (!validateURIString(content.uri)) {
595
+ throw new Error("If provided, content.uri must be a valid URI string");
596
+ }
597
+ if (typeof content.mime !== "string") {
598
+ throw new Error("If provided, content.mime must be a string");
599
+ }
600
+ }
601
+ return true;
602
+ }
603
+
604
+ // src/metadata/validateMetadataURIContent.ts
605
+ async function validateMetadataURIContent(metadataURI) {
606
+ let response;
607
+ const cleanedURI = cleanAndValidateMetadataURI(metadataURI);
608
+ try {
609
+ response = await fetch(cleanedURI);
610
+ } catch (error) {
611
+ const errorMessage = error instanceof Error ? error.message : String(error);
612
+ throw new Error(
613
+ `Metadata fetch failed for URL '${cleanedURI}': ${errorMessage}`
614
+ );
615
+ }
616
+ if (!response.ok) {
617
+ throw new Error(
618
+ `Metadata fetch failed for URL '${cleanedURI}': ${response.statusText ? `${response.statusText} (HTTP ${response.status})` : `HTTP ${response.status}`}`
619
+ );
620
+ }
621
+ if (!["application/json", "text/plain"].includes(
622
+ response.headers.get("content-type") ?? ""
623
+ )) {
624
+ throw new Error("Metadata is not a valid JSON or plain text response type");
625
+ }
626
+ const metadataJson = await response.json();
627
+ return validateMetadataJSON(metadataJson);
628
+ }
629
+
630
+ // src/utils/calls.ts
631
+ import {
632
+ concatHex,
633
+ encodeFunctionData
634
+ } from "viem";
635
+ var EMPTY_HEX = "0x";
636
+ var isContractCall = (call) => {
637
+ return call.address !== void 0 && call.abi !== void 0 && call.functionName !== void 0;
638
+ };
639
+ var isSendCall = (call) => {
640
+ return !isContractCall(call) && call.to !== void 0;
641
+ };
642
+ function toGenericCall(call) {
643
+ if (isSendCall(call)) {
644
+ return {
645
+ to: call.to,
646
+ value: call.value ?? 0n,
647
+ data: EMPTY_HEX
648
+ };
649
+ }
650
+ const { dataSuffix } = call;
651
+ const callData = encodeFunctionData(call);
652
+ const data = dataSuffix ? concatHex([callData, dataSuffix]) : callData;
653
+ return {
654
+ to: call.address,
655
+ value: call.value ?? 0n,
656
+ data
657
+ };
658
+ }
659
+ function toUserOperationCalls(calls) {
660
+ return calls.map((call) => ({
661
+ to: call.to,
662
+ data: call.data,
663
+ value: call.value
664
+ }));
665
+ }
666
+
667
+ // src/utils/getChainFromId.ts
668
+ import { base, baseSepolia } from "viem/chains";
669
+ function getChainFromId(chainId) {
670
+ if (chainId === base.id) {
671
+ return base;
672
+ }
673
+ if (chainId === baseSepolia.id) {
674
+ return baseSepolia;
675
+ }
676
+ throw new Error(`Chain ID ${chainId} not supported`);
677
+ }
678
+
646
679
  // src/utils/rethrowDecodedRevert.ts
647
680
  import {
648
681
  BaseError,
@@ -678,6 +711,89 @@ function rethrowDecodedRevert(err, abi) {
678
711
  throw err;
679
712
  }
680
713
 
714
+ // src/utils/userOperation.ts
715
+ import { formatEther } from "viem";
716
+ var prepareUserOperation = async ({
717
+ bundlerClient,
718
+ account,
719
+ calls
720
+ }) => {
721
+ const prepared = await bundlerClient.prepareUserOperation({
722
+ account,
723
+ calls
724
+ });
725
+ return prepared;
726
+ };
727
+ var submitUserOperation = async ({
728
+ bundlerClient,
729
+ account,
730
+ userOperation
731
+ }) => {
732
+ let hash;
733
+ const signature = await account.signUserOperation(userOperation);
734
+ try {
735
+ hash = await bundlerClient.sendUserOperation({
736
+ account,
737
+ ...userOperation,
738
+ signature
739
+ });
740
+ } catch (error) {
741
+ if (isGasError(error)) {
742
+ throw new CoinbaseGasError(error);
743
+ }
744
+ throw error;
745
+ }
746
+ return bundlerClient.waitForUserOperationReceipt({ hash });
747
+ };
748
+ var CoinbaseGasError = class extends Error {
749
+ constructor(error) {
750
+ let message;
751
+ let available;
752
+ let required;
753
+ const match = error.details.match(
754
+ /precheck failed: sender balance and deposit together is (\d+)? but must be at least (\d+)? to pay for this operation/
755
+ );
756
+ if (match) {
757
+ available = match[1] ? BigInt(match[1]) : void 0;
758
+ required = match[2] ? BigInt(match[2]) : void 0;
759
+ if (available !== void 0 && required !== void 0) {
760
+ message = `Insufficient balance. You need at least ${formatEther(required)} ETH to pay for this operation, but you only have ${formatEther(available)} ETH.`;
761
+ } else if (required !== void 0) {
762
+ message = `Insufficient balance. Make sure you have at least ${formatEther(required)} ETH in your wallet.`;
763
+ } else {
764
+ message = `Insufficient balance. Make sure you have enough ETH to pay for this operation.`;
765
+ }
766
+ } else {
767
+ message = error.details ?? error.message;
768
+ }
769
+ super(message);
770
+ this.cause = error.cause;
771
+ this.details = error.details;
772
+ this.available = available;
773
+ this.required = required;
774
+ }
775
+ };
776
+ function isGasError(error) {
777
+ return error.details?.startsWith(
778
+ "precheck failed: sender balance and deposit together is"
779
+ ) ?? false;
780
+ }
781
+
782
+ // src/utils/validateClientNetwork.ts
783
+ import { base as base2, baseSepolia as baseSepolia2 } from "viem/chains";
784
+ var validateClientNetwork = (publicClient) => {
785
+ const clientChainId = publicClient?.chain?.id;
786
+ if (clientChainId === base2.id) {
787
+ return;
788
+ }
789
+ if (clientChainId === baseSepolia2.id) {
790
+ return;
791
+ }
792
+ throw new Error(
793
+ "Client network needs to be base or baseSepolia for current coin deployments."
794
+ );
795
+ };
796
+
681
797
  // src/actions/createCoin.ts
682
798
  var STARTING_MARKET_CAPS = {
683
799
  LOW: "LOW",
@@ -703,7 +819,8 @@ async function createCoinCall({
703
819
  payoutRecipientOverride,
704
820
  additionalOwners,
705
821
  platformReferrer,
706
- skipMetadataValidation = false
822
+ skipMetadataValidation = false,
823
+ enableSmartWalletRouting
707
824
  }) {
708
825
  if (!skipMetadataValidation) {
709
826
  await validateMetadataURIContent(metadata.uri);
@@ -717,7 +834,8 @@ async function createCoinCall({
717
834
  symbol,
718
835
  platformReferrer,
719
836
  additionalOwners,
720
- payoutRecipientOverride
837
+ payoutRecipientOverride,
838
+ enableSmartWalletRouting
721
839
  });
722
840
  if (!createContentRequest.data?.calls) {
723
841
  throw new Error("Failed to create content calldata");
@@ -728,36 +846,19 @@ async function createCoinCall({
728
846
  data: data.data,
729
847
  value: BigInt(data.value)
730
848
  })),
731
- predictedCoinAddress: createContentRequest.data.predictedCoinAddress
849
+ predictedCoinAddress: createContentRequest.data.predictedCoinAddress,
850
+ usedSmartWalletRouting: createContentRequest.data.usedSmartWalletRouting
732
851
  };
733
852
  }
734
- function getCoinCreateFromLogs(receipt) {
735
- const eventLogs = parseEventLogs({
736
- abi: zoraFactoryImplABI,
737
- logs: receipt.logs
738
- });
739
- return eventLogs.find((log) => log.eventName === "CoinCreatedV4")?.args;
740
- }
741
- async function createCoin({
742
- call,
743
- walletClient,
744
- publicClient,
745
- options
746
- }) {
747
- validateClientNetwork(publicClient);
748
- const chainId = call.chainId ?? publicClient.chain.id;
749
- const callRequest = await createCoinCall({
750
- ...call,
751
- chainId
752
- });
753
- if (callRequest.calls.length !== 1) {
853
+ function validateCreateCoinCalls(calls, chainId) {
854
+ if (calls.length !== 1) {
754
855
  throw new Error("Only one call is supported for this SDK version");
755
856
  }
756
- const createContentCall = callRequest.calls[0];
857
+ const createContentCall = calls[0];
757
858
  if (!createContentCall) {
758
859
  throw new Error("Failed to load create content calldata from API");
759
860
  }
760
- const coinFactoryAddressForChain = coinFactoryAddress[call.chainId];
861
+ const coinFactoryAddressForChain = coinFactoryAddress[chainId];
761
862
  if (!isAddressEqual(createContentCall.to, coinFactoryAddressForChain)) {
762
863
  throw new Error("Creator coin is not supported for this SDK version");
763
864
  }
@@ -766,22 +867,87 @@ async function createCoin({
766
867
  "Creator coin and purchase is not supported for this SDK version."
767
868
  );
768
869
  }
769
- const selectedAccount = (typeof options?.account === "string" ? void 0 : options?.account) ?? walletClient.account;
770
- if (!selectedAccount) {
870
+ }
871
+ function validateCreateCoinSmartWalletCalls(calls, { usedSmartWalletRouting }) {
872
+ if (!usedSmartWalletRouting) {
873
+ throw new Error(
874
+ "Smart wallet routing was not applied. The creator must have a linked smart wallet; otherwise use createCoin for EOA creation."
875
+ );
876
+ }
877
+ if (calls.length !== 1) {
878
+ throw new Error("Only one call is supported for this SDK version");
879
+ }
880
+ const createContentCall = calls[0];
881
+ if (!createContentCall) {
882
+ throw new Error("Failed to load create content calldata from API");
883
+ }
884
+ if (createContentCall.value !== 0n) {
885
+ throw new Error(
886
+ "Creator coin and purchase is not supported for this SDK version."
887
+ );
888
+ }
889
+ }
890
+ function getCoinCreateFromLogs(receipt) {
891
+ const eventLogs = parseEventLogs({
892
+ abi: zoraFactoryImplABI,
893
+ logs: receipt.logs
894
+ });
895
+ return eventLogs.find((log) => log.eventName === "CoinCreatedV4")?.args;
896
+ }
897
+ var coinbaseSmartWalletExecuteABI = [
898
+ {
899
+ type: "function",
900
+ name: "execute",
901
+ stateMutability: "payable",
902
+ inputs: [
903
+ { name: "target", type: "address" },
904
+ { name: "value", type: "uint256" },
905
+ { name: "data", type: "bytes" }
906
+ ],
907
+ outputs: []
908
+ }
909
+ ];
910
+ function unwrapSmartWalletExecuteCall(call) {
911
+ let decoded;
912
+ try {
913
+ decoded = decodeFunctionData({
914
+ abi: coinbaseSmartWalletExecuteABI,
915
+ data: call.data
916
+ });
917
+ } catch {
918
+ throw new Error(
919
+ "Expected a smart wallet `execute` call from smart wallet routing, but the routed call could not be decoded."
920
+ );
921
+ }
922
+ const [target, value, data] = decoded.args;
923
+ return { to: target, value, data };
924
+ }
925
+ function selectExecutionAccount(walletClient, account) {
926
+ const selected = (typeof account === "string" ? void 0 : account) ?? walletClient.account;
927
+ if (!selected) {
771
928
  throw new Error("Account is required");
772
929
  }
930
+ return selected;
931
+ }
932
+ async function executeCreateContentCall({
933
+ createContentCall,
934
+ account,
935
+ walletClient,
936
+ publicClient,
937
+ skipValidateTransaction
938
+ }) {
773
939
  const viemCall = {
774
940
  ...createContentCall,
775
- account: selectedAccount
941
+ account
776
942
  };
777
- if (!options?.skipValidateTransaction) {
943
+ if (!skipValidateTransaction) {
778
944
  try {
779
945
  await publicClient.call(viemCall);
780
946
  } catch (err) {
781
947
  rethrowDecodedRevert(err, zoraFactoryImplABI);
782
948
  }
783
949
  }
784
- const gasEstimate = options?.skipValidateTransaction ? 10000000n : await publicClient.estimateGas(viemCall);
950
+ const gasEstimate = skipValidateTransaction ? 10000000n : await publicClient.estimateGas(viemCall);
785
951
  const gasPrice = await publicClient.getGasPrice();
786
952
  const hash = await (async () => {
787
953
  try {
@@ -807,6 +973,77 @@ async function createCoin({
807
973
  chain: getChainFromId(publicClient.chain.id)
808
974
  };
809
975
  }
976
+ async function createCoin({
977
+ call,
978
+ walletClient,
979
+ publicClient,
980
+ options
981
+ }) {
982
+ validateClientNetwork(publicClient);
983
+ const chainId = call.chainId ?? publicClient.chain.id;
984
+ const { calls } = await createCoinCall({
985
+ ...call,
986
+ chainId
987
+ });
988
+ validateCreateCoinCalls(calls, chainId);
989
+ const createContentCall = calls[0];
990
+ const account = selectExecutionAccount(walletClient, options?.account);
991
+ return executeCreateContentCall({
992
+ createContentCall,
993
+ account,
994
+ walletClient,
995
+ publicClient,
996
+ skipValidateTransaction: options?.skipValidateTransaction
997
+ });
998
+ }
999
+ async function createCoinSmartWallet({
1000
+ call,
1001
+ bundlerClient,
1002
+ publicClient
1003
+ }) {
1004
+ validateClientNetwork(publicClient);
1005
+ const account = bundlerClient.account;
1006
+ if (!account) {
1007
+ throw new Error("Account is required: the bundler client has no account");
1008
+ }
1009
+ const chainId = call.chainId ?? publicClient.chain.id;
1010
+ const { calls, usedSmartWalletRouting } = await createCoinCall({
1011
+ ...call,
1012
+ chainId,
1013
+ enableSmartWalletRouting: true
1014
+ });
1015
+ validateCreateCoinSmartWalletCalls(calls, { usedSmartWalletRouting });
1016
+ const innerCall = unwrapSmartWalletExecuteCall(calls[0]);
1017
+ const userOperation = await prepareUserOperation({
1018
+ bundlerClient,
1019
+ account,
1020
+ calls: toUserOperationCalls([innerCall])
1021
+ });
1022
+ const userOpReceipt = await submitUserOperation({
1023
+ bundlerClient,
1024
+ account,
1025
+ userOperation
1026
+ });
1027
+ if (!userOpReceipt.success) {
1028
+ throw new Error(
1029
+ `User operation reverted${userOpReceipt.reason ? `: ${userOpReceipt.reason}` : ""}`
1030
+ );
1031
+ }
1032
+ const eventLogs = parseEventLogs({
1033
+ abi: zoraFactoryImplABI,
1034
+ logs: userOpReceipt.logs
1035
+ });
1036
+ const deployment = eventLogs.find(
1037
+ (log) => log.eventName === "CoinCreatedV4"
1038
+ )?.args;
1039
+ return {
1040
+ hash: userOpReceipt.receipt.transactionHash,
1041
+ receipt: userOpReceipt.receipt,
1042
+ address: deployment?.coin,
1043
+ deployment,
1044
+ chain: getChainFromId(publicClient.chain.id)
1045
+ };
1046
+ }
810
1047
 
811
1048
  // src/actions/updateCoinURI.ts
812
1049
  import { coinABI } from "@zoralabs/protocol-deployments";
@@ -822,13 +1059,14 @@ function getAttribution() {
822
1059
  }
823
1060
 
824
1061
  // src/actions/updateCoinURI.ts
825
- function updateCoinURICall({
826
- newURI,
827
- coin
828
- }) {
1062
+ function validateUpdateCoinURI({ newURI }) {
829
1063
  if (!newURI.startsWith("ipfs://")) {
830
1064
  throw new Error("URI needs to be an ipfs:// prefix uri");
831
1065
  }
1066
+ }
1067
+ function updateCoinURICall(args) {
1068
+ validateUpdateCoinURI(args);
1069
+ const { coin, newURI } = args;
832
1070
  return {
833
1071
  abi: coinABI,
834
1072
  address: coin,
@@ -852,16 +1090,56 @@ async function updateCoinURI(args, walletClient, publicClient, account) {
852
1090
  );
853
1091
  return { hash, receipt, uriUpdated };
854
1092
  }
1093
+ async function updateCoinURISmartWallet(args, bundlerClient, publicClient, account) {
1094
+ const resolvedAccount = account ?? bundlerClient.account;
1095
+ if (!resolvedAccount) {
1096
+ throw new Error("Account is required");
1097
+ }
1098
+ validateClientNetwork(publicClient);
1099
+ const call = updateCoinURICall(args);
1100
+ const calls = toUserOperationCalls([toGenericCall(call)]);
1101
+ const userOp = await prepareUserOperation({
1102
+ bundlerClient,
1103
+ account: resolvedAccount,
1104
+ calls
1105
+ });
1106
+ const userOpReceipt = await submitUserOperation({
1107
+ bundlerClient,
1108
+ account: resolvedAccount,
1109
+ userOperation: userOp
1110
+ });
1111
+ if (!userOpReceipt.success) {
1112
+ throw new Error(
1113
+ `User operation reverted${userOpReceipt.reason ? `: ${userOpReceipt.reason}` : ""}`
1114
+ );
1115
+ }
1116
+ const eventLogs = parseEventLogs2({ abi: coinABI, logs: userOpReceipt.logs });
1117
+ const uriUpdated = eventLogs.find(
1118
+ (log) => log.eventName === "ContractURIUpdated"
1119
+ );
1120
+ return {
1121
+ hash: userOpReceipt.receipt.transactionHash,
1122
+ receipt: userOpReceipt.receipt,
1123
+ uriUpdated
1124
+ };
1125
+ }
855
1126
 
856
1127
  // src/actions/updatePayoutRecipient.ts
857
1128
  import { coinABI as coinABI2 } from "@zoralabs/protocol-deployments";
858
1129
  import {
1130
+ isAddress,
859
1131
  parseEventLogs as parseEventLogs3
860
1132
  } from "viem";
861
- function updatePayoutRecipientCall({
862
- newPayoutRecipient,
863
- coin
1133
+ function validateUpdatePayoutRecipient({
1134
+ newPayoutRecipient
864
1135
  }) {
1136
+ if (!isAddress(newPayoutRecipient)) {
1137
+ throw new Error("Payout recipient must be a valid address");
1138
+ }
1139
+ }
1140
+ function updatePayoutRecipientCall(args) {
1141
+ validateUpdatePayoutRecipient(args);
1142
+ const { coin, newPayoutRecipient } = args;
865
1143
  return {
866
1144
  abi: coinABI2,
867
1145
  address: coin,
@@ -885,10 +1163,44 @@ async function updatePayoutRecipient(args, walletClient, publicClient, account)
885
1163
  );
886
1164
  return { hash, receipt, payoutRecipientUpdated };
887
1165
  }
1166
+ async function updatePayoutRecipientSmartWallet(args, bundlerClient, publicClient, account) {
1167
+ const resolvedAccount = account ?? bundlerClient.account;
1168
+ if (!resolvedAccount) {
1169
+ throw new Error("Account is required");
1170
+ }
1171
+ validateClientNetwork(publicClient);
1172
+ const call = updatePayoutRecipientCall(args);
1173
+ const calls = toUserOperationCalls([toGenericCall(call)]);
1174
+ const userOp = await prepareUserOperation({
1175
+ bundlerClient,
1176
+ account: resolvedAccount,
1177
+ calls
1178
+ });
1179
+ const userOpReceipt = await submitUserOperation({
1180
+ bundlerClient,
1181
+ account: resolvedAccount,
1182
+ userOperation: userOp
1183
+ });
1184
+ if (!userOpReceipt.success) {
1185
+ throw new Error(
1186
+ `User operation reverted${userOpReceipt.reason ? `: ${userOpReceipt.reason}` : ""}`
1187
+ );
1188
+ }
1189
+ const eventLogs = parseEventLogs3({ abi: coinABI2, logs: userOpReceipt.logs });
1190
+ const payoutRecipientUpdated = eventLogs.find(
1191
+ (log) => log.eventName === "CoinPayoutRecipientUpdated"
1192
+ );
1193
+ return {
1194
+ hash: userOpReceipt.receipt.transactionHash,
1195
+ receipt: userOpReceipt.receipt,
1196
+ payoutRecipientUpdated
1197
+ };
1198
+ }
888
1199
 
889
1200
  // src/actions/tradeCoin.ts
890
1201
  import { permit2ABI, permit2Address } from "@zoralabs/protocol-deployments";
891
1202
  import {
1203
+ encodeFunctionData as encodeFunctionData2,
892
1204
  erc20Abi,
893
1205
  maxUint256
894
1206
  } from "viem";
@@ -916,6 +1228,73 @@ var PERMIT_SINGLE_TYPES = {
916
1228
  { name: "nonce", type: "uint48" }
917
1229
  ]
918
1230
  };
1231
+ async function resolveTradePermits({
1232
+ quote,
1233
+ owner,
1234
+ publicClient,
1235
+ signTypedData
1236
+ }) {
1237
+ const signatures = [];
1238
+ const approvalCalls = [];
1239
+ if (!quote.permits) {
1240
+ return { signatures, approvalCalls };
1241
+ }
1242
+ for (const permit of quote.permits) {
1243
+ const [, , nonce] = await publicClient.readContract({
1244
+ abi: permit2ABI,
1245
+ address: permit2Address[base4.id],
1246
+ functionName: "allowance",
1247
+ args: [
1248
+ owner,
1249
+ permit.permit.details.token,
1250
+ permit.permit.spender
1251
+ ]
1252
+ });
1253
+ const permitToken = permit.permit.details.token;
1254
+ const allowance = await publicClient.readContract({
1255
+ abi: erc20Abi,
1256
+ address: permitToken,
1257
+ functionName: "allowance",
1258
+ args: [owner, permit2Address[base4.id]]
1259
+ });
1260
+ if (allowance < BigInt(permit.permit.details.amount)) {
1261
+ approvalCalls.push({
1262
+ to: permitToken,
1263
+ data: encodeFunctionData2({
1264
+ abi: erc20Abi,
1265
+ functionName: "approve",
1266
+ args: [permit2Address[base4.id], maxUint256]
1267
+ }),
1268
+ value: 0n
1269
+ });
1270
+ }
1271
+ const message = {
1272
+ details: {
1273
+ token: permit.permit.details.token,
1274
+ amount: BigInt(permit.permit.details.amount),
1275
+ expiration: Number(permit.permit.details.expiration),
1276
+ nonce
1277
+ },
1278
+ spender: permit.permit.spender,
1279
+ sigDeadline: BigInt(permit.permit.sigDeadline)
1280
+ };
1281
+ const signature = await signTypedData({
1282
+ domain: {
1283
+ name: "Permit2",
1284
+ chainId: base4.id,
1285
+ verifyingContract: permit2Address[base4.id]
1286
+ },
1287
+ primaryType: "PermitSingle",
1288
+ types: PERMIT_SINGLE_TYPES,
1289
+ message
1290
+ });
1291
+ signatures.push({
1292
+ signature,
1293
+ permit: convertBigIntToString(message)
1294
+ });
1295
+ }
1296
+ return { signatures, approvalCalls };
1297
+ }
919
1298
  async function tradeCoin({
920
1299
  tradeParameters,
921
1300
  walletClient,
@@ -930,71 +1309,24 @@ async function tradeCoin({
930
1309
  if (!account) {
931
1310
  throw new Error("Account is required");
932
1311
  }
1312
+ const resolvedAccount = account;
1313
+ const owner = typeof resolvedAccount === "string" ? resolvedAccount : resolvedAccount.address;
933
1314
  if (!tradeParameters.recipient) {
934
- tradeParameters.recipient = typeof account === "string" ? account : account.address;
1315
+ tradeParameters.recipient = owner;
935
1316
  }
936
- const signatures = [];
937
- if (quote.permits) {
938
- for (const permit of quote.permits) {
939
- const [, , nonce] = await publicClient.readContract({
940
- abi: permit2ABI,
941
- address: permit2Address[base4.id],
942
- functionName: "allowance",
943
- args: [
944
- typeof account === "string" ? account : account.address,
945
- permit.permit.details.token,
946
- permit.permit.spender
947
- ]
948
- });
949
- const permitToken = permit.permit.details.token;
950
- const allowance = await publicClient.readContract({
951
- abi: erc20Abi,
952
- address: permitToken,
953
- functionName: "allowance",
954
- args: [
955
- typeof account === "string" ? account : account.address,
956
- permit2Address[base4.id]
957
- ]
958
- });
959
- if (allowance < BigInt(permit.permit.details.amount)) {
960
- const approvalTx = await walletClient.writeContract({
961
- abi: erc20Abi,
962
- address: permitToken,
963
- functionName: "approve",
964
- chain: base4,
965
- args: [permit2Address[base4.id], maxUint256],
966
- account
967
- });
968
- await publicClient.waitForTransactionReceipt({
969
- hash: approvalTx
970
- });
971
- }
972
- const message = {
973
- details: {
974
- token: permit.permit.details.token,
975
- amount: BigInt(permit.permit.details.amount),
976
- expiration: Number(permit.permit.details.expiration),
977
- nonce
978
- },
979
- spender: permit.permit.spender,
980
- sigDeadline: BigInt(permit.permit.sigDeadline)
981
- };
982
- const signature = await walletClient.signTypedData({
983
- domain: {
984
- name: "Permit2",
985
- chainId: base4.id,
986
- verifyingContract: permit2Address[base4.id]
987
- },
988
- primaryType: "PermitSingle",
989
- types: PERMIT_SINGLE_TYPES,
990
- message,
991
- account
992
- });
993
- signatures.push({
994
- signature,
995
- permit: convertBigIntToString(message)
996
- });
997
- }
1317
+ const { signatures, approvalCalls } = await resolveTradePermits({
1318
+ quote,
1319
+ owner,
1320
+ publicClient,
1321
+ signTypedData: (typedData) => walletClient.signTypedData({ ...typedData, account: resolvedAccount })
1322
+ });
1323
+ for (const approvalCall of approvalCalls) {
1324
+ const approvalTx = await walletClient.sendTransaction({
1325
+ ...approvalCall,
1326
+ account: resolvedAccount,
1327
+ chain: base4
1328
+ });
1329
+ await publicClient.waitForTransactionReceipt({ hash: approvalTx });
998
1330
  }
999
1331
  const newQuote = await createTradeCall({
1000
1332
  ...tradeParameters,
@@ -1005,7 +1337,7 @@ async function tradeCoin({
1005
1337
  data: newQuote.call.data,
1006
1338
  value: BigInt(newQuote.call.value),
1007
1339
  chain: base4,
1008
- account
1340
+ account: resolvedAccount
1009
1341
  };
1010
1342
  if (validateTransaction) {
1011
1343
  await publicClient.call(call);
@@ -1022,13 +1354,69 @@ async function tradeCoin({
1022
1354
  });
1023
1355
  return receipt;
1024
1356
  }
1025
- async function createTradeCall(tradeParameters) {
1357
+ async function tradeCoinSmartWallet({
1358
+ tradeParameters,
1359
+ bundlerClient,
1360
+ account,
1361
+ publicClient
1362
+ }) {
1363
+ const resolvedAccount = account ?? bundlerClient.account;
1364
+ if (!resolvedAccount) {
1365
+ throw new Error("Account is required");
1366
+ }
1367
+ const owner = resolvedAccount.address;
1368
+ const params = {
1369
+ ...tradeParameters,
1370
+ sender: owner,
1371
+ recipient: tradeParameters.recipient ?? owner
1372
+ };
1373
+ const quote = await createTradeCall(params);
1374
+ const { signatures, approvalCalls } = await resolveTradePermits({
1375
+ quote,
1376
+ owner,
1377
+ publicClient,
1378
+ signTypedData: (typedData) => resolvedAccount.signTypedData(typedData)
1379
+ });
1380
+ const newQuote = await createTradeCall({
1381
+ ...params,
1382
+ signatures
1383
+ });
1384
+ const tradeCall = {
1385
+ to: newQuote.call.target,
1386
+ data: newQuote.call.data,
1387
+ value: BigInt(newQuote.call.value)
1388
+ };
1389
+ const calls = toUserOperationCalls([...approvalCalls, tradeCall]);
1390
+ const userOp = await prepareUserOperation({
1391
+ bundlerClient,
1392
+ account: resolvedAccount,
1393
+ calls
1394
+ });
1395
+ const userOpReceipt = await submitUserOperation({
1396
+ bundlerClient,
1397
+ account: resolvedAccount,
1398
+ userOperation: userOp
1399
+ });
1400
+ if (!userOpReceipt.success) {
1401
+ throw new Error(
1402
+ `User operation reverted${userOpReceipt.reason ? `: ${userOpReceipt.reason}` : ""}`
1403
+ );
1404
+ }
1405
+ return userOpReceipt.receipt;
1406
+ }
1407
+ function validateTradeParameters(tradeParameters) {
1026
1408
  if (tradeParameters.slippage && tradeParameters.slippage > 1) {
1027
1409
  throw new Error("Slippage must be less than 1, max 0.99");
1028
1410
  }
1029
1411
  if (tradeParameters.amountIn === BigInt(0)) {
1030
1412
  throw new Error("Amount in must be greater than 0");
1031
1413
  }
1414
+ }
1415
+ async function createTradeCall(tradeParameters) {
1416
+ return createQuote(tradeParameters);
1417
+ }
1418
+ async function createQuote(tradeParameters) {
1419
+ validateTradeParameters(tradeParameters);
1032
1420
  const quote = await postQuote({
1033
1421
  body: {
1034
1422
  tokenIn: tradeParameters.sell,
@@ -1057,6 +1445,12 @@ async function createTradeCall(tradeParameters) {
1057
1445
  import { createConfig as createConfig2 } from "@hey-api/client-fetch";
1058
1446
  var apiGet = (path, data) => client.get({ url: path, query: data, ...getApiKeyMeta() });
1059
1447
  var apiPost = (path, data) => client.post({ url: path, body: data, ...getApiKeyMeta() });
1448
+ var apiUrl = (path) => {
1449
+ const baseUrl = client.getConfig().baseUrl ?? "";
1450
+ const normalizedBase = baseUrl.replace(/\/+$/, "");
1451
+ const normalizedPath = path.replace(/^\/+/, "");
1452
+ return `${normalizedBase}/${normalizedPath}`;
1453
+ };
1060
1454
  var setApiBaseUrl = (baseUrl) => {
1061
1455
  client.setConfig(createConfig2({ baseUrl }));
1062
1456
  };
@@ -1309,14 +1703,18 @@ function createZoraUploaderForCreator(creatorAddress) {
1309
1703
  }
1310
1704
  export {
1311
1705
  CoinMetadataBuilder,
1706
+ CoinbaseGasError,
1312
1707
  CreateConstants,
1313
1708
  ZoraUploader,
1314
1709
  apiGet,
1315
1710
  apiPost,
1711
+ apiUrl,
1316
1712
  cleanAndValidateMetadataURI,
1317
1713
  createCoin,
1318
1714
  createCoinCall,
1715
+ createCoinSmartWallet,
1319
1716
  createMetadataBuilder,
1717
+ createQuote,
1320
1718
  createTradeCall,
1321
1719
  createZoraUploaderForCreator,
1322
1720
  getCoin2 as getCoin,
@@ -1363,15 +1761,29 @@ export {
1363
1761
  getTrends,
1364
1762
  getURLFromUploadResult,
1365
1763
  getWalletTradeActivity2 as getWalletTradeActivity,
1764
+ isContractCall,
1765
+ isSendCall,
1766
+ prepareUserOperation,
1366
1767
  setApiBaseUrl,
1367
1768
  setApiKey,
1769
+ submitUserOperation,
1770
+ toGenericCall,
1771
+ toUserOperationCalls,
1368
1772
  tradeCoin,
1773
+ tradeCoinSmartWallet,
1369
1774
  updateCoinURI,
1370
1775
  updateCoinURICall,
1776
+ updateCoinURISmartWallet,
1371
1777
  updatePayoutRecipient,
1372
1778
  updatePayoutRecipientCall,
1779
+ updatePayoutRecipientSmartWallet,
1780
+ validateCreateCoinCalls,
1781
+ validateCreateCoinSmartWalletCalls,
1373
1782
  validateImageMimeType,
1374
1783
  validateMetadataJSON,
1375
- validateMetadataURIContent
1784
+ validateMetadataURIContent,
1785
+ validateTradeParameters,
1786
+ validateUpdateCoinURI,
1787
+ validateUpdatePayoutRecipient
1376
1788
  };
1377
1789
  //# sourceMappingURL=index.js.map