@rareprotocol/rare-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -4
- package/dist/client.d.ts +2144 -0
- package/dist/client.js +3125 -0
- package/dist/index.js +997 -573
- package/package.json +18 -1
package/dist/index.js
CHANGED
|
@@ -218,6 +218,12 @@ function getWalletClient(chain) {
|
|
|
218
218
|
};
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
// src/sdk/client.ts
|
|
222
|
+
import {
|
|
223
|
+
parseEther,
|
|
224
|
+
parseEventLogs
|
|
225
|
+
} from "viem";
|
|
226
|
+
|
|
221
227
|
// src/contracts/abis/factory.ts
|
|
222
228
|
var factoryAbi = [
|
|
223
229
|
{
|
|
@@ -383,71 +389,6 @@ var factoryAbi = [
|
|
|
383
389
|
}
|
|
384
390
|
];
|
|
385
391
|
|
|
386
|
-
// src/commands/deploy.ts
|
|
387
|
-
function deployErc721Command() {
|
|
388
|
-
const cmd = new Command2("erc721");
|
|
389
|
-
cmd.description("Deploy a new ERC-721 NFT contract via the RARE factory");
|
|
390
|
-
cmd.argument("<name>", "name of the NFT collection").argument("<symbol>", "symbol of the NFT collection").option("--max-tokens <number>", "maximum number of tokens (optional)").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (name, symbol, opts) => {
|
|
391
|
-
const chain = getActiveChain(opts.chain);
|
|
392
|
-
const { client, account } = getWalletClient(chain);
|
|
393
|
-
const publicClient = getPublicClient(chain);
|
|
394
|
-
const factoryAddress = getContractAddresses(chain).factory;
|
|
395
|
-
console.log(`Deploying ERC-721 contract on ${chain}...`);
|
|
396
|
-
console.log(` Factory: ${factoryAddress}`);
|
|
397
|
-
console.log(` Name: ${name}`);
|
|
398
|
-
console.log(` Symbol: ${symbol}`);
|
|
399
|
-
if (opts.maxTokens) console.log(` Max tokens: ${opts.maxTokens}`);
|
|
400
|
-
let txHash;
|
|
401
|
-
if (opts.maxTokens) {
|
|
402
|
-
txHash = await client.writeContract({
|
|
403
|
-
address: factoryAddress,
|
|
404
|
-
abi: factoryAbi,
|
|
405
|
-
functionName: "createSovereignBatchMint",
|
|
406
|
-
args: [name, symbol, BigInt(opts.maxTokens)],
|
|
407
|
-
account,
|
|
408
|
-
chain: void 0
|
|
409
|
-
});
|
|
410
|
-
} else {
|
|
411
|
-
txHash = await client.writeContract({
|
|
412
|
-
address: factoryAddress,
|
|
413
|
-
abi: factoryAbi,
|
|
414
|
-
functionName: "createSovereignBatchMint",
|
|
415
|
-
args: [name, symbol],
|
|
416
|
-
account,
|
|
417
|
-
chain: void 0
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
console.log(`Transaction sent: ${txHash}`);
|
|
421
|
-
console.log("Waiting for confirmation...");
|
|
422
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
423
|
-
const { parseEventLogs } = await import("viem");
|
|
424
|
-
const logs = parseEventLogs({
|
|
425
|
-
abi: factoryAbi,
|
|
426
|
-
logs: receipt.logs,
|
|
427
|
-
eventName: "SovereignBatchMintCreated"
|
|
428
|
-
});
|
|
429
|
-
if (logs.length > 0) {
|
|
430
|
-
const deployedAddress = logs[0].args.contractAddress;
|
|
431
|
-
console.log(`
|
|
432
|
-
ERC-721 contract deployed at: ${deployedAddress}`);
|
|
433
|
-
} else {
|
|
434
|
-
console.log(`
|
|
435
|
-
Transaction confirmed. Block: ${receipt.blockNumber}`);
|
|
436
|
-
console.log("Could not parse deployed address from logs.");
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
return cmd;
|
|
440
|
-
}
|
|
441
|
-
function deployCommand() {
|
|
442
|
-
const cmd = new Command2("deploy");
|
|
443
|
-
cmd.description("Deploy a new contract via the RARE protocol");
|
|
444
|
-
cmd.addCommand(deployErc721Command());
|
|
445
|
-
return cmd;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// src/commands/mint.ts
|
|
449
|
-
import { Command as Command3 } from "commander";
|
|
450
|
-
|
|
451
392
|
// src/contracts/abis/token.ts
|
|
452
393
|
var tokenAbi = [
|
|
453
394
|
{
|
|
@@ -1206,303 +1147,43 @@ var tokenAbi = [
|
|
|
1206
1147
|
}
|
|
1207
1148
|
];
|
|
1208
1149
|
|
|
1209
|
-
// src/
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
".svg": "image/svg+xml",
|
|
1221
|
-
".mp4": "video/mp4",
|
|
1222
|
-
".mov": "video/quicktime",
|
|
1223
|
-
".webm": "video/webm",
|
|
1224
|
-
".glb": "model/gltf-binary",
|
|
1225
|
-
".gltf": "model/gltf+json",
|
|
1226
|
-
".html": "text/html"
|
|
1227
|
-
};
|
|
1228
|
-
function inferMimeType(filename) {
|
|
1229
|
-
const ext = extname(filename).toLowerCase();
|
|
1230
|
-
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
1231
|
-
}
|
|
1232
|
-
function assertPositiveInteger(value, fieldName) {
|
|
1233
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
1234
|
-
throw new Error(`${fieldName} must be a positive integer`);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
function assertEvmAddress(value, fieldName) {
|
|
1238
|
-
if (!isAddress(value)) {
|
|
1239
|
-
throw new Error(`${fieldName} must be a valid EVM address`);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
function parseDimensions(dimensions) {
|
|
1243
|
-
if (!dimensions) return void 0;
|
|
1244
|
-
const [w, h] = dimensions.split("x");
|
|
1245
|
-
if (!w || !h) return void 0;
|
|
1246
|
-
const width = parseInt(w, 10);
|
|
1247
|
-
const height = parseInt(h, 10);
|
|
1248
|
-
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return void 0;
|
|
1249
|
-
return { width, height };
|
|
1250
|
-
}
|
|
1251
|
-
async function apiPost(path2, payload) {
|
|
1252
|
-
const url = `${API_BASE_URL}${path2}`;
|
|
1253
|
-
const response = await fetch(url, {
|
|
1254
|
-
method: "POST",
|
|
1255
|
-
headers: { "Content-Type": "application/json" },
|
|
1256
|
-
body: JSON.stringify(payload)
|
|
1257
|
-
});
|
|
1258
|
-
const text = await response.text();
|
|
1259
|
-
const json = text ? JSON.parse(text) : {};
|
|
1260
|
-
if (!response.ok) {
|
|
1261
|
-
const message = json.error ?? text;
|
|
1262
|
-
throw new Error(`API error ${response.status} on ${path2}: ${message}`);
|
|
1263
|
-
}
|
|
1264
|
-
return json;
|
|
1265
|
-
}
|
|
1266
|
-
async function uploadParts(fileBuffer, partSize, presignedUrls) {
|
|
1267
|
-
const parts = [];
|
|
1268
|
-
for (let i = 0; i < presignedUrls.length; i++) {
|
|
1269
|
-
const start = i * partSize;
|
|
1270
|
-
const end = start + partSize;
|
|
1271
|
-
const partBuffer = fileBuffer.subarray(start, end);
|
|
1272
|
-
const response = await fetch(presignedUrls[i], {
|
|
1273
|
-
method: "PUT",
|
|
1274
|
-
body: new Uint8Array(partBuffer)
|
|
1275
|
-
});
|
|
1276
|
-
if (response.status !== 200 && response.status !== 204) {
|
|
1277
|
-
throw new Error(`Part ${i + 1} upload failed with status ${response.status}`);
|
|
1278
|
-
}
|
|
1279
|
-
const etag = response.headers.get("etag");
|
|
1280
|
-
if (!etag) {
|
|
1281
|
-
throw new Error(`Missing etag header for part ${i + 1}`);
|
|
1282
|
-
}
|
|
1283
|
-
parts.push({ ETag: etag, PartNumber: i + 1 });
|
|
1284
|
-
}
|
|
1285
|
-
return parts;
|
|
1286
|
-
}
|
|
1287
|
-
async function uploadMedia(filePath, label) {
|
|
1288
|
-
const fileStats = await stat(filePath);
|
|
1289
|
-
const fileSize = fileStats.size;
|
|
1290
|
-
const fileName = basename(filePath);
|
|
1291
|
-
const fileBuffer = await readFile(filePath);
|
|
1292
|
-
const mimeType = inferMimeType(fileName);
|
|
1293
|
-
console.log(`Uploading ${label}: ${fileName} (${fileSize} bytes, ${mimeType})`);
|
|
1294
|
-
const init = await apiPost("/api/nft/media-upload-url", {
|
|
1295
|
-
fileSize,
|
|
1296
|
-
filename: fileName
|
|
1297
|
-
});
|
|
1298
|
-
console.log(` Multipart upload initialized (${init.presignedUrls.length} parts)`);
|
|
1299
|
-
const parts = await uploadParts(fileBuffer, init.partSize, init.presignedUrls);
|
|
1300
|
-
console.log(` All parts uploaded`);
|
|
1301
|
-
const complete = await apiPost("/api/nft/media-upload-complete", {
|
|
1302
|
-
key: init.key,
|
|
1303
|
-
uploadId: init.uploadId,
|
|
1304
|
-
bucket: init.bucket,
|
|
1305
|
-
parts
|
|
1306
|
-
});
|
|
1307
|
-
console.log(` Upload complete: ${complete.ipfsUrl}`);
|
|
1308
|
-
const generated = await apiPost("/api/nft/media-generate", {
|
|
1309
|
-
uri: complete.ipfsUrl,
|
|
1310
|
-
mimeType
|
|
1311
|
-
});
|
|
1312
|
-
const dimensions = parseDimensions(generated.media.dimensions);
|
|
1313
|
-
const entry = {
|
|
1314
|
-
url: generated.media.uri,
|
|
1315
|
-
mimeType: generated.media.mimeType,
|
|
1316
|
-
size: generated.media.size ?? fileSize,
|
|
1317
|
-
...dimensions ? { dimensions } : {}
|
|
1318
|
-
};
|
|
1319
|
-
console.log(` Media generated: ${entry.url}`);
|
|
1320
|
-
return entry;
|
|
1321
|
-
}
|
|
1322
|
-
async function importErc721(opts) {
|
|
1323
|
-
assertPositiveInteger(opts.chainId, "chainId");
|
|
1324
|
-
assertEvmAddress(opts.contractAddress, "contractAddress");
|
|
1325
|
-
assertEvmAddress(opts.ownerAddress, "ownerAddress");
|
|
1326
|
-
const normalizedContractAddress = opts.contractAddress.toLowerCase();
|
|
1327
|
-
const normalizedOwnerAddress = opts.ownerAddress.toLowerCase();
|
|
1328
|
-
const result = await apiPost("/api/nft/import-erc721", {
|
|
1329
|
-
chainId: opts.chainId,
|
|
1330
|
-
contractAddress: normalizedContractAddress,
|
|
1331
|
-
ownerAddress: normalizedOwnerAddress
|
|
1332
|
-
});
|
|
1333
|
-
if (result.ok !== true) {
|
|
1334
|
-
throw new Error("Unexpected response from /api/nft/import-erc721");
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
async function pinMetadata(opts) {
|
|
1338
|
-
const nftMedia = {
|
|
1339
|
-
image: opts.image
|
|
1340
|
-
};
|
|
1341
|
-
if (opts.video) {
|
|
1342
|
-
nftMedia.video = opts.video;
|
|
1343
|
-
}
|
|
1344
|
-
const payload = {
|
|
1345
|
-
name: opts.name,
|
|
1346
|
-
description: opts.description,
|
|
1347
|
-
nftMedia,
|
|
1348
|
-
tags: opts.tags ?? []
|
|
1349
|
-
};
|
|
1350
|
-
if (opts.attributes && opts.attributes.length > 0) {
|
|
1351
|
-
payload.attributes = opts.attributes;
|
|
1352
|
-
}
|
|
1353
|
-
console.log("Pinning metadata to IPFS...");
|
|
1354
|
-
const result = await apiPost("/api/nft/metadata", payload);
|
|
1355
|
-
console.log(`Metadata pinned: ${result.ipfsUrl}`);
|
|
1356
|
-
console.log(`Gateway URL: ${result.gatewayUrl}`);
|
|
1357
|
-
return result.ipfsUrl;
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
// src/commands/mint.ts
|
|
1361
|
-
function parseAttribute(raw) {
|
|
1362
|
-
if (raw.startsWith("{")) {
|
|
1363
|
-
const parsed = JSON.parse(raw);
|
|
1364
|
-
if (parsed.value === void 0) {
|
|
1365
|
-
throw new Error(`Attribute JSON must include "value": ${raw}`);
|
|
1366
|
-
}
|
|
1367
|
-
return parsed;
|
|
1368
|
-
}
|
|
1369
|
-
const eqIndex = raw.indexOf("=");
|
|
1370
|
-
if (eqIndex === -1) {
|
|
1371
|
-
return { value: raw };
|
|
1372
|
-
}
|
|
1373
|
-
const trait_type = raw.slice(0, eqIndex);
|
|
1374
|
-
const rawValue = raw.slice(eqIndex + 1);
|
|
1375
|
-
const numValue = Number(rawValue);
|
|
1376
|
-
const value = rawValue.length > 0 && !Number.isNaN(numValue) ? numValue : rawValue;
|
|
1377
|
-
return { trait_type, value };
|
|
1378
|
-
}
|
|
1379
|
-
function mintCommand() {
|
|
1380
|
-
const cmd = new Command3("mint");
|
|
1381
|
-
cmd.description("Mint a new NFT on a deployed token contract");
|
|
1382
|
-
cmd.requiredOption("--contract <address>", "token contract address").option("--token-uri <uri>", "token metadata URI (skip upload if provided)").option("--name <name>", "NFT name").option("--description <description>", "NFT description").option("--image <path>", "path to image file").option("--video <path>", "path to video file").option("--tag <tag>", "tag (repeatable)", (val, acc) => [...acc, val], []).option("--attribute <attr>", 'attribute as "trait=value" or JSON (repeatable)', (val, acc) => [...acc, val], []).option("--to <address>", "recipient address (defaults to caller)").option("--royalty-receiver <address>", "royalty receiver address (defaults to caller)").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (opts) => {
|
|
1383
|
-
let tokenUri;
|
|
1384
|
-
if (opts.tokenUri) {
|
|
1385
|
-
tokenUri = opts.tokenUri;
|
|
1386
|
-
} else {
|
|
1387
|
-
if (!opts.name) {
|
|
1388
|
-
console.error("Error: --name is required when not using --token-uri");
|
|
1389
|
-
process.exit(1);
|
|
1150
|
+
// src/contracts/abis/auction.ts
|
|
1151
|
+
var auctionAbi = [
|
|
1152
|
+
{
|
|
1153
|
+
"type": "function",
|
|
1154
|
+
"name": "COLDIE_AUCTION",
|
|
1155
|
+
"inputs": [],
|
|
1156
|
+
"outputs": [
|
|
1157
|
+
{
|
|
1158
|
+
"name": "",
|
|
1159
|
+
"type": "bytes32",
|
|
1160
|
+
"internalType": "bytes32"
|
|
1390
1161
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1162
|
+
],
|
|
1163
|
+
"stateMutability": "view"
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
"type": "function",
|
|
1167
|
+
"name": "NO_AUCTION",
|
|
1168
|
+
"inputs": [],
|
|
1169
|
+
"outputs": [
|
|
1170
|
+
{
|
|
1171
|
+
"name": "",
|
|
1172
|
+
"type": "bytes32",
|
|
1173
|
+
"internalType": "bytes32"
|
|
1394
1174
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
name:
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
video: videoMedia,
|
|
1408
|
-
tags,
|
|
1409
|
-
attributes
|
|
1410
|
-
});
|
|
1411
|
-
}
|
|
1412
|
-
const chain = getActiveChain(opts.chain);
|
|
1413
|
-
const { client, account } = getWalletClient(chain);
|
|
1414
|
-
const publicClient = getPublicClient(chain);
|
|
1415
|
-
const contractAddress = opts.contract;
|
|
1416
|
-
const useMintTo = opts.to || opts.royaltyReceiver;
|
|
1417
|
-
console.log(`
|
|
1418
|
-
Minting NFT on ${chain}...`);
|
|
1419
|
-
console.log(` Contract: ${contractAddress}`);
|
|
1420
|
-
console.log(` URI: ${tokenUri}`);
|
|
1421
|
-
let txHash;
|
|
1422
|
-
if (useMintTo) {
|
|
1423
|
-
const receiver = opts.to ?? account.address;
|
|
1424
|
-
const royaltyReceiver = opts.royaltyReceiver ?? account.address;
|
|
1425
|
-
console.log(` To: ${receiver}`);
|
|
1426
|
-
console.log(` Royalty receiver: ${royaltyReceiver}`);
|
|
1427
|
-
txHash = await client.writeContract({
|
|
1428
|
-
address: contractAddress,
|
|
1429
|
-
abi: tokenAbi,
|
|
1430
|
-
functionName: "mintTo",
|
|
1431
|
-
args: [tokenUri, receiver, royaltyReceiver],
|
|
1432
|
-
account,
|
|
1433
|
-
chain: void 0
|
|
1434
|
-
});
|
|
1435
|
-
} else {
|
|
1436
|
-
txHash = await client.writeContract({
|
|
1437
|
-
address: contractAddress,
|
|
1438
|
-
abi: tokenAbi,
|
|
1439
|
-
functionName: "addNewToken",
|
|
1440
|
-
args: [tokenUri],
|
|
1441
|
-
account,
|
|
1442
|
-
chain: void 0
|
|
1443
|
-
});
|
|
1444
|
-
}
|
|
1445
|
-
console.log(`Transaction sent: ${txHash}`);
|
|
1446
|
-
console.log("Waiting for confirmation...");
|
|
1447
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
1448
|
-
const { parseEventLogs } = await import("viem");
|
|
1449
|
-
const logs = parseEventLogs({
|
|
1450
|
-
abi: tokenAbi,
|
|
1451
|
-
logs: receipt.logs,
|
|
1452
|
-
eventName: "Transfer"
|
|
1453
|
-
});
|
|
1454
|
-
if (logs.length > 0) {
|
|
1455
|
-
console.log(`
|
|
1456
|
-
NFT minted! Token ID: ${logs[0].args.tokenId}`);
|
|
1457
|
-
} else {
|
|
1458
|
-
console.log(`
|
|
1459
|
-
Transaction confirmed. Block: ${receipt.blockNumber}`);
|
|
1460
|
-
}
|
|
1461
|
-
});
|
|
1462
|
-
return cmd;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
// src/commands/auction.ts
|
|
1466
|
-
import { Command as Command4 } from "commander";
|
|
1467
|
-
import { parseEther, formatEther } from "viem";
|
|
1468
|
-
|
|
1469
|
-
// src/contracts/abis/auction.ts
|
|
1470
|
-
var auctionAbi = [
|
|
1471
|
-
{
|
|
1472
|
-
"type": "function",
|
|
1473
|
-
"name": "COLDIE_AUCTION",
|
|
1474
|
-
"inputs": [],
|
|
1475
|
-
"outputs": [
|
|
1476
|
-
{
|
|
1477
|
-
"name": "",
|
|
1478
|
-
"type": "bytes32",
|
|
1479
|
-
"internalType": "bytes32"
|
|
1480
|
-
}
|
|
1481
|
-
],
|
|
1482
|
-
"stateMutability": "view"
|
|
1483
|
-
},
|
|
1484
|
-
{
|
|
1485
|
-
"type": "function",
|
|
1486
|
-
"name": "NO_AUCTION",
|
|
1487
|
-
"inputs": [],
|
|
1488
|
-
"outputs": [
|
|
1489
|
-
{
|
|
1490
|
-
"name": "",
|
|
1491
|
-
"type": "bytes32",
|
|
1492
|
-
"internalType": "bytes32"
|
|
1493
|
-
}
|
|
1494
|
-
],
|
|
1495
|
-
"stateMutability": "view"
|
|
1496
|
-
},
|
|
1497
|
-
{
|
|
1498
|
-
"type": "function",
|
|
1499
|
-
"name": "SCHEDULED_AUCTION",
|
|
1500
|
-
"inputs": [],
|
|
1501
|
-
"outputs": [
|
|
1502
|
-
{
|
|
1503
|
-
"name": "",
|
|
1504
|
-
"type": "bytes32",
|
|
1505
|
-
"internalType": "bytes32"
|
|
1175
|
+
],
|
|
1176
|
+
"stateMutability": "view"
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
"type": "function",
|
|
1180
|
+
"name": "SCHEDULED_AUCTION",
|
|
1181
|
+
"inputs": [],
|
|
1182
|
+
"outputs": [
|
|
1183
|
+
{
|
|
1184
|
+
"name": "",
|
|
1185
|
+
"type": "bytes32",
|
|
1186
|
+
"internalType": "bytes32"
|
|
1506
1187
|
}
|
|
1507
1188
|
],
|
|
1508
1189
|
"stateMutability": "view"
|
|
@@ -3037,34 +2718,177 @@ var auctionAbi = [
|
|
|
3037
2718
|
}
|
|
3038
2719
|
];
|
|
3039
2720
|
|
|
3040
|
-
// src/
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
2721
|
+
// src/sdk/api.ts
|
|
2722
|
+
import { isAddress } from "viem";
|
|
2723
|
+
var API_BASE_URL = "https://api.superrare.org";
|
|
2724
|
+
var MIME_TYPES = {
|
|
2725
|
+
".png": "image/png",
|
|
2726
|
+
".jpg": "image/jpeg",
|
|
2727
|
+
".jpeg": "image/jpeg",
|
|
2728
|
+
".gif": "image/gif",
|
|
2729
|
+
".webp": "image/webp",
|
|
2730
|
+
".svg": "image/svg+xml",
|
|
2731
|
+
".mp4": "video/mp4",
|
|
2732
|
+
".mov": "video/quicktime",
|
|
2733
|
+
".webm": "video/webm",
|
|
2734
|
+
".glb": "model/gltf-binary",
|
|
2735
|
+
".gltf": "model/gltf+json",
|
|
2736
|
+
".html": "text/html"
|
|
2737
|
+
};
|
|
2738
|
+
function inferMimeType(filename) {
|
|
2739
|
+
const extIndex = filename.lastIndexOf(".");
|
|
2740
|
+
const ext = extIndex === -1 ? "" : filename.slice(extIndex).toLowerCase();
|
|
2741
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2742
|
+
}
|
|
2743
|
+
function normalizeFilename(filename) {
|
|
2744
|
+
const normalized = filename.replaceAll("\\", "/");
|
|
2745
|
+
const lastSeparator = normalized.lastIndexOf("/");
|
|
2746
|
+
return lastSeparator === -1 ? normalized : normalized.slice(lastSeparator + 1);
|
|
2747
|
+
}
|
|
2748
|
+
function assertPositiveInteger(value, fieldName) {
|
|
2749
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
2750
|
+
throw new Error(`${fieldName} must be a positive integer`);
|
|
3045
2751
|
}
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
2752
|
+
}
|
|
2753
|
+
function assertEvmAddress(value, fieldName) {
|
|
2754
|
+
if (!isAddress(value)) {
|
|
2755
|
+
throw new Error(`${fieldName} must be a valid EVM address`);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
function parseDimensions(dimensions) {
|
|
2759
|
+
if (!dimensions) return void 0;
|
|
2760
|
+
const [w, h] = dimensions.split("x");
|
|
2761
|
+
if (!w || !h) return void 0;
|
|
2762
|
+
const width = Number.parseInt(w, 10);
|
|
2763
|
+
const height = Number.parseInt(h, 10);
|
|
2764
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
2765
|
+
return void 0;
|
|
2766
|
+
}
|
|
2767
|
+
return { width, height };
|
|
2768
|
+
}
|
|
2769
|
+
async function apiPost(path2, payload) {
|
|
2770
|
+
const url = `${API_BASE_URL}${path2}`;
|
|
2771
|
+
const response = await fetch(url, {
|
|
2772
|
+
method: "POST",
|
|
2773
|
+
headers: { "Content-Type": "application/json" },
|
|
2774
|
+
body: JSON.stringify(payload)
|
|
2775
|
+
});
|
|
2776
|
+
const text = await response.text();
|
|
2777
|
+
const json = text ? JSON.parse(text) : {};
|
|
2778
|
+
if (!response.ok) {
|
|
2779
|
+
const message = json.error ?? text;
|
|
2780
|
+
throw new Error(`API error ${response.status} on ${path2}: ${String(message)}`);
|
|
2781
|
+
}
|
|
2782
|
+
return json;
|
|
2783
|
+
}
|
|
2784
|
+
async function uploadParts(fileBuffer, partSize, presignedUrls) {
|
|
2785
|
+
const parts = [];
|
|
2786
|
+
for (let i = 0; i < presignedUrls.length; i++) {
|
|
2787
|
+
const start = i * partSize;
|
|
2788
|
+
const end = start + partSize;
|
|
2789
|
+
const partBuffer = fileBuffer.subarray(start, end);
|
|
2790
|
+
const response = await fetch(presignedUrls[i], {
|
|
2791
|
+
method: "PUT",
|
|
2792
|
+
body: new Uint8Array(partBuffer)
|
|
2793
|
+
});
|
|
2794
|
+
if (response.status !== 200 && response.status !== 204) {
|
|
2795
|
+
throw new Error(`Part ${i + 1} upload failed with status ${response.status}`);
|
|
3055
2796
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
}
|
|
2797
|
+
const etag = response.headers.get("etag");
|
|
2798
|
+
if (!etag) {
|
|
2799
|
+
throw new Error(`Missing etag header for part ${i + 1}`);
|
|
3060
2800
|
}
|
|
3061
|
-
|
|
3062
|
-
depth++;
|
|
2801
|
+
parts.push({ ETag: etag, PartNumber: i + 1 });
|
|
3063
2802
|
}
|
|
3064
|
-
|
|
2803
|
+
return parts;
|
|
2804
|
+
}
|
|
2805
|
+
async function uploadMedia(buffer, filename) {
|
|
2806
|
+
const fileSize = buffer.byteLength;
|
|
2807
|
+
const safeFilename = normalizeFilename(filename);
|
|
2808
|
+
const mimeType = inferMimeType(safeFilename);
|
|
2809
|
+
const init = await apiPost("/api/nft/media-upload-url", {
|
|
2810
|
+
fileSize,
|
|
2811
|
+
filename: safeFilename
|
|
2812
|
+
});
|
|
2813
|
+
const parts = await uploadParts(buffer, init.partSize, init.presignedUrls);
|
|
2814
|
+
const complete = await apiPost("/api/nft/media-upload-complete", {
|
|
2815
|
+
key: init.key,
|
|
2816
|
+
uploadId: init.uploadId,
|
|
2817
|
+
bucket: init.bucket,
|
|
2818
|
+
parts
|
|
2819
|
+
});
|
|
2820
|
+
const generated = await apiPost("/api/nft/media-generate", {
|
|
2821
|
+
uri: complete.ipfsUrl,
|
|
2822
|
+
mimeType
|
|
2823
|
+
});
|
|
2824
|
+
const dimensions = parseDimensions(generated.media.dimensions);
|
|
2825
|
+
return {
|
|
2826
|
+
url: generated.media.uri,
|
|
2827
|
+
mimeType: generated.media.mimeType,
|
|
2828
|
+
size: generated.media.size ?? fileSize,
|
|
2829
|
+
...dimensions ? { dimensions } : {}
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
async function pinMetadata(opts) {
|
|
2833
|
+
const nftMedia = {
|
|
2834
|
+
image: opts.image
|
|
2835
|
+
};
|
|
2836
|
+
if (opts.video) {
|
|
2837
|
+
nftMedia.video = opts.video;
|
|
2838
|
+
}
|
|
2839
|
+
const payload = {
|
|
2840
|
+
name: opts.name,
|
|
2841
|
+
description: opts.description,
|
|
2842
|
+
nftMedia,
|
|
2843
|
+
tags: opts.tags ?? []
|
|
2844
|
+
};
|
|
2845
|
+
if (opts.attributes && opts.attributes.length > 0) {
|
|
2846
|
+
payload.attributes = opts.attributes;
|
|
2847
|
+
}
|
|
2848
|
+
const result = await apiPost("/api/nft/metadata", payload);
|
|
2849
|
+
return result.ipfsUrl;
|
|
2850
|
+
}
|
|
2851
|
+
async function importErc721(opts) {
|
|
2852
|
+
assertPositiveInteger(opts.chainId, "chainId");
|
|
2853
|
+
assertEvmAddress(opts.contract, "contract");
|
|
2854
|
+
assertEvmAddress(opts.owner, "owner");
|
|
2855
|
+
const result = await apiPost("/api/nft/import-erc721", {
|
|
2856
|
+
chainId: opts.chainId,
|
|
2857
|
+
contractAddress: opts.contract.toLowerCase(),
|
|
2858
|
+
ownerAddress: opts.owner.toLowerCase()
|
|
2859
|
+
});
|
|
2860
|
+
if (result.ok !== true) {
|
|
2861
|
+
throw new Error("Unexpected response from /api/nft/import-erc721");
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
async function searchPost(path2, payload) {
|
|
2865
|
+
return apiPost(path2, payload);
|
|
2866
|
+
}
|
|
2867
|
+
async function searchNfts(params = {}) {
|
|
2868
|
+
return searchPost("/api/search/nfts", {
|
|
2869
|
+
query: params.query ?? "",
|
|
2870
|
+
take: params.take ?? 24,
|
|
2871
|
+
cursor: params.cursor ?? 0,
|
|
2872
|
+
sortBy: params.sortBy ?? "RECENT_ACTIVITY_DESC",
|
|
2873
|
+
ownerAddresses: params.ownerAddresses ?? [],
|
|
2874
|
+
creatorAddresses: params.creatorAddresses ?? [],
|
|
2875
|
+
collectionIds: params.collectionIds ?? [],
|
|
2876
|
+
contractAddresses: params.contractAddresses ?? [],
|
|
2877
|
+
...params.auctionStates ? { auctionStates: params.auctionStates } : {},
|
|
2878
|
+
...params.chainIds ? { chainIds: params.chainIds } : {}
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
async function searchCollections(params = {}) {
|
|
2882
|
+
return searchPost("/api/search/collections", {
|
|
2883
|
+
query: params.query ?? "",
|
|
2884
|
+
take: params.take ?? 24,
|
|
2885
|
+
cursor: params.cursor ?? 0,
|
|
2886
|
+
sortBy: params.sortBy ?? "NEWEST",
|
|
2887
|
+
ownerAddresses: params.ownerAddresses ?? []
|
|
2888
|
+
});
|
|
3065
2889
|
}
|
|
3066
2890
|
|
|
3067
|
-
// src/
|
|
2891
|
+
// src/sdk/client.ts
|
|
3068
2892
|
var ETH_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
3069
2893
|
var approvalAbi = [
|
|
3070
2894
|
{
|
|
@@ -3082,183 +2906,809 @@ var approvalAbi = [
|
|
|
3082
2906
|
type: "function"
|
|
3083
2907
|
}
|
|
3084
2908
|
];
|
|
3085
|
-
function
|
|
3086
|
-
const
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
const currency = opts.currency ?? ETH_ADDRESS;
|
|
3094
|
-
console.log(`Creating auction on ${chain}...`);
|
|
3095
|
-
console.log(` Auction contract: ${auctionAddress}`);
|
|
3096
|
-
console.log(` NFT contract: ${opts.contract}`);
|
|
3097
|
-
console.log(` Token ID: ${opts.tokenId}`);
|
|
3098
|
-
console.log(` Starting price: ${opts.startingPrice} ETH`);
|
|
3099
|
-
console.log(` Duration: ${opts.duration} seconds`);
|
|
3100
|
-
const nftAddress = opts.contract;
|
|
3101
|
-
const isApproved = await publicClient.readContract({
|
|
3102
|
-
address: nftAddress,
|
|
3103
|
-
abi: approvalAbi,
|
|
3104
|
-
functionName: "isApprovedForAll",
|
|
3105
|
-
args: [account.address, auctionAddress]
|
|
3106
|
-
});
|
|
3107
|
-
if (!isApproved) {
|
|
3108
|
-
console.log("\nApproval required. Requesting setApprovalForAll...");
|
|
3109
|
-
const approveTxHash = await client.writeContract({
|
|
3110
|
-
address: nftAddress,
|
|
3111
|
-
abi: approvalAbi,
|
|
3112
|
-
functionName: "setApprovalForAll",
|
|
3113
|
-
args: [auctionAddress, true],
|
|
3114
|
-
account,
|
|
3115
|
-
chain: void 0
|
|
3116
|
-
});
|
|
3117
|
-
console.log(`Approval tx sent: ${approveTxHash}`);
|
|
3118
|
-
await publicClient.waitForTransactionReceipt({ hash: approveTxHash });
|
|
3119
|
-
console.log("Approval confirmed.\n");
|
|
3120
|
-
} else {
|
|
3121
|
-
console.log("(Already approved)\n");
|
|
2909
|
+
function resolveChainFromPublicClient(publicClient) {
|
|
2910
|
+
const chainId = publicClient.chain?.id;
|
|
2911
|
+
if (!chainId) {
|
|
2912
|
+
throw new Error("Unable to resolve chain from publicClient.chain.id. Create your public client with an explicit chain.");
|
|
2913
|
+
}
|
|
2914
|
+
for (const [chain, id] of Object.entries(chainIds)) {
|
|
2915
|
+
if (id === chainId) {
|
|
2916
|
+
return chain;
|
|
3122
2917
|
}
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
2918
|
+
}
|
|
2919
|
+
throw new Error(`Unsupported chain id: ${chainId}. Supported chain ids: ${Object.values(chainIds).join(", ")}`);
|
|
2920
|
+
}
|
|
2921
|
+
function requireWallet(config) {
|
|
2922
|
+
if (!config.walletClient) {
|
|
2923
|
+
throw new Error("walletClient is required for write operations.");
|
|
2924
|
+
}
|
|
2925
|
+
const walletAccount = config.walletClient.account;
|
|
2926
|
+
if (config.account) {
|
|
2927
|
+
if (walletAccount && walletAccount.address.toLowerCase() === config.account.toLowerCase()) {
|
|
2928
|
+
return {
|
|
2929
|
+
walletClient: config.walletClient,
|
|
2930
|
+
account: walletAccount,
|
|
2931
|
+
accountAddress: walletAccount.address
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
return {
|
|
2935
|
+
walletClient: config.walletClient,
|
|
2936
|
+
account: config.account,
|
|
2937
|
+
accountAddress: config.account
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
if (!walletAccount) {
|
|
2941
|
+
throw new Error("No account available for write operations. Pass config.account or provide walletClient with an account.");
|
|
2942
|
+
}
|
|
2943
|
+
return {
|
|
2944
|
+
walletClient: config.walletClient,
|
|
2945
|
+
account: walletAccount,
|
|
2946
|
+
accountAddress: walletAccount.address
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
function toInteger(value, field) {
|
|
2950
|
+
if (typeof value === "bigint") return value;
|
|
2951
|
+
if (typeof value === "number") {
|
|
2952
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
2953
|
+
throw new Error(`${field} must be an integer.`);
|
|
2954
|
+
}
|
|
2955
|
+
return BigInt(value);
|
|
2956
|
+
}
|
|
2957
|
+
try {
|
|
2958
|
+
return BigInt(value);
|
|
2959
|
+
} catch {
|
|
2960
|
+
throw new Error(`${field} must be an integer.`);
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
function toWei(value) {
|
|
2964
|
+
if (typeof value === "bigint") {
|
|
2965
|
+
return value;
|
|
2966
|
+
}
|
|
2967
|
+
return parseEther(String(value));
|
|
2968
|
+
}
|
|
2969
|
+
function createRareClient(config) {
|
|
2970
|
+
const { publicClient } = config;
|
|
2971
|
+
const chain = resolveChainFromPublicClient(publicClient);
|
|
2972
|
+
const chainId = chainIds[chain];
|
|
2973
|
+
const addresses = getContractAddresses(chain);
|
|
2974
|
+
return {
|
|
2975
|
+
chain,
|
|
2976
|
+
chainId,
|
|
2977
|
+
contracts: {
|
|
2978
|
+
factory: addresses.factory,
|
|
2979
|
+
auction: addresses.auction
|
|
2980
|
+
},
|
|
2981
|
+
deploy: {
|
|
2982
|
+
async erc721(params) {
|
|
2983
|
+
const { walletClient, account } = requireWallet(config);
|
|
2984
|
+
let txHash;
|
|
2985
|
+
if (params.maxTokens !== void 0) {
|
|
2986
|
+
txHash = await walletClient.writeContract({
|
|
2987
|
+
address: addresses.factory,
|
|
2988
|
+
abi: factoryAbi,
|
|
2989
|
+
functionName: "createSovereignBatchMint",
|
|
2990
|
+
args: [params.name, params.symbol, toInteger(params.maxTokens, "maxTokens")],
|
|
2991
|
+
account,
|
|
2992
|
+
chain: void 0
|
|
2993
|
+
});
|
|
2994
|
+
} else {
|
|
2995
|
+
txHash = await walletClient.writeContract({
|
|
2996
|
+
address: addresses.factory,
|
|
2997
|
+
abi: factoryAbi,
|
|
2998
|
+
functionName: "createSovereignBatchMint",
|
|
2999
|
+
args: [params.name, params.symbol],
|
|
3000
|
+
account,
|
|
3001
|
+
chain: void 0
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3005
|
+
const logs = parseEventLogs({
|
|
3006
|
+
abi: factoryAbi,
|
|
3007
|
+
logs: receipt.logs,
|
|
3008
|
+
eventName: "SovereignBatchMintCreated"
|
|
3009
|
+
});
|
|
3010
|
+
return {
|
|
3011
|
+
txHash,
|
|
3012
|
+
receipt,
|
|
3013
|
+
contract: logs[0]?.args.contractAddress
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
},
|
|
3017
|
+
mint: {
|
|
3018
|
+
async mintTo(params) {
|
|
3019
|
+
const { walletClient, account, accountAddress } = requireWallet(config);
|
|
3020
|
+
const useMintTo = Boolean(params.to || params.royaltyReceiver);
|
|
3021
|
+
let txHash;
|
|
3022
|
+
if (useMintTo) {
|
|
3023
|
+
const receiver = params.to ?? accountAddress;
|
|
3024
|
+
const royaltyReceiver = params.royaltyReceiver ?? accountAddress;
|
|
3025
|
+
txHash = await walletClient.writeContract({
|
|
3026
|
+
address: params.contract,
|
|
3027
|
+
abi: tokenAbi,
|
|
3028
|
+
functionName: "mintTo",
|
|
3029
|
+
args: [params.tokenUri, receiver, royaltyReceiver],
|
|
3030
|
+
account,
|
|
3031
|
+
chain: void 0
|
|
3032
|
+
});
|
|
3033
|
+
} else {
|
|
3034
|
+
txHash = await walletClient.writeContract({
|
|
3035
|
+
address: params.contract,
|
|
3036
|
+
abi: tokenAbi,
|
|
3037
|
+
functionName: "addNewToken",
|
|
3038
|
+
args: [params.tokenUri],
|
|
3039
|
+
account,
|
|
3040
|
+
chain: void 0
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3044
|
+
const logs = parseEventLogs({
|
|
3045
|
+
abi: tokenAbi,
|
|
3046
|
+
logs: receipt.logs,
|
|
3047
|
+
eventName: "Transfer"
|
|
3048
|
+
});
|
|
3049
|
+
return {
|
|
3050
|
+
txHash,
|
|
3051
|
+
receipt,
|
|
3052
|
+
tokenId: logs[0]?.args.tokenId
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
},
|
|
3056
|
+
auction: {
|
|
3057
|
+
async create(params) {
|
|
3058
|
+
const { walletClient, account, accountAddress } = requireWallet(config);
|
|
3059
|
+
const nftAddress = params.contract;
|
|
3060
|
+
const currency = params.currency ?? ETH_ADDRESS;
|
|
3061
|
+
const tokenId = toInteger(params.tokenId, "tokenId");
|
|
3062
|
+
const startingPrice = toWei(params.startingPrice);
|
|
3063
|
+
const duration = toInteger(params.duration, "duration");
|
|
3064
|
+
const splitAddresses = params.splitAddresses ?? [accountAddress];
|
|
3065
|
+
const splitRatios = params.splitRatios ?? [100];
|
|
3066
|
+
let approvalTxHash;
|
|
3067
|
+
if (params.autoApprove !== false) {
|
|
3068
|
+
const isApproved = await publicClient.readContract({
|
|
3069
|
+
address: nftAddress,
|
|
3070
|
+
abi: approvalAbi,
|
|
3071
|
+
functionName: "isApprovedForAll",
|
|
3072
|
+
args: [accountAddress, addresses.auction]
|
|
3073
|
+
});
|
|
3074
|
+
if (!isApproved) {
|
|
3075
|
+
approvalTxHash = await walletClient.writeContract({
|
|
3076
|
+
address: nftAddress,
|
|
3077
|
+
abi: approvalAbi,
|
|
3078
|
+
functionName: "setApprovalForAll",
|
|
3079
|
+
args: [addresses.auction, true],
|
|
3080
|
+
account,
|
|
3081
|
+
chain: void 0
|
|
3082
|
+
});
|
|
3083
|
+
await publicClient.waitForTransactionReceipt({ hash: approvalTxHash });
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
const auctionType = await publicClient.readContract({
|
|
3087
|
+
address: addresses.auction,
|
|
3088
|
+
abi: auctionAbi,
|
|
3089
|
+
functionName: "COLDIE_AUCTION"
|
|
3090
|
+
});
|
|
3091
|
+
const txHash = await walletClient.writeContract({
|
|
3092
|
+
address: addresses.auction,
|
|
3093
|
+
abi: auctionAbi,
|
|
3094
|
+
functionName: "configureAuction",
|
|
3095
|
+
args: [
|
|
3096
|
+
auctionType,
|
|
3097
|
+
nftAddress,
|
|
3098
|
+
tokenId,
|
|
3099
|
+
startingPrice,
|
|
3100
|
+
currency,
|
|
3101
|
+
duration,
|
|
3102
|
+
0n,
|
|
3103
|
+
splitAddresses,
|
|
3104
|
+
splitRatios
|
|
3105
|
+
],
|
|
3106
|
+
account,
|
|
3107
|
+
chain: void 0
|
|
3108
|
+
});
|
|
3109
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3110
|
+
return {
|
|
3111
|
+
txHash,
|
|
3112
|
+
receipt,
|
|
3113
|
+
approvalTxHash
|
|
3114
|
+
};
|
|
3115
|
+
},
|
|
3116
|
+
async bid(params) {
|
|
3117
|
+
const { walletClient, account } = requireWallet(config);
|
|
3118
|
+
const currency = params.currency ?? ETH_ADDRESS;
|
|
3119
|
+
const amount = toWei(params.amount);
|
|
3120
|
+
const isEth = currency === ETH_ADDRESS;
|
|
3121
|
+
const txHash = await walletClient.writeContract({
|
|
3122
|
+
address: addresses.auction,
|
|
3123
|
+
abi: auctionAbi,
|
|
3124
|
+
functionName: "bid",
|
|
3125
|
+
args: [params.contract, toInteger(params.tokenId, "tokenId"), currency, amount],
|
|
3126
|
+
account,
|
|
3127
|
+
chain: void 0,
|
|
3128
|
+
value: isEth ? amount : 0n
|
|
3129
|
+
});
|
|
3130
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3131
|
+
return { txHash, receipt };
|
|
3132
|
+
},
|
|
3133
|
+
async settle(params) {
|
|
3134
|
+
const { walletClient, account } = requireWallet(config);
|
|
3135
|
+
const txHash = await walletClient.writeContract({
|
|
3136
|
+
address: addresses.auction,
|
|
3137
|
+
abi: auctionAbi,
|
|
3138
|
+
functionName: "settleAuction",
|
|
3139
|
+
args: [params.contract, toInteger(params.tokenId, "tokenId")],
|
|
3140
|
+
account,
|
|
3141
|
+
chain: void 0
|
|
3142
|
+
});
|
|
3143
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3144
|
+
return { txHash, receipt };
|
|
3145
|
+
},
|
|
3146
|
+
async cancel(params) {
|
|
3147
|
+
const { walletClient, account } = requireWallet(config);
|
|
3148
|
+
const txHash = await walletClient.writeContract({
|
|
3149
|
+
address: addresses.auction,
|
|
3150
|
+
abi: auctionAbi,
|
|
3151
|
+
functionName: "cancelAuction",
|
|
3152
|
+
args: [params.contract, toInteger(params.tokenId, "tokenId")],
|
|
3153
|
+
account,
|
|
3154
|
+
chain: void 0
|
|
3155
|
+
});
|
|
3156
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3157
|
+
return { txHash, receipt };
|
|
3158
|
+
},
|
|
3159
|
+
async getStatus(params) {
|
|
3160
|
+
const result = await publicClient.readContract({
|
|
3161
|
+
address: addresses.auction,
|
|
3162
|
+
abi: auctionAbi,
|
|
3163
|
+
functionName: "getAuctionDetails",
|
|
3164
|
+
args: [params.contract, toInteger(params.tokenId, "tokenId")]
|
|
3165
|
+
});
|
|
3166
|
+
const [
|
|
3167
|
+
seller,
|
|
3168
|
+
creationBlock,
|
|
3169
|
+
startingTime,
|
|
3170
|
+
lengthOfAuction,
|
|
3150
3171
|
currency,
|
|
3151
|
-
|
|
3152
|
-
|
|
3172
|
+
minimumBid,
|
|
3173
|
+
auctionType,
|
|
3153
3174
|
splitAddresses,
|
|
3154
3175
|
splitRatios
|
|
3155
|
-
]
|
|
3156
|
-
|
|
3157
|
-
|
|
3176
|
+
] = result;
|
|
3177
|
+
const started = startingTime > 0n;
|
|
3178
|
+
const endTime = started ? startingTime + lengthOfAuction : null;
|
|
3179
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
3180
|
+
let status = "PENDING";
|
|
3181
|
+
if (started) {
|
|
3182
|
+
status = endTime !== null && now >= endTime ? "ENDED" : "RUNNING";
|
|
3183
|
+
}
|
|
3184
|
+
return {
|
|
3185
|
+
seller,
|
|
3186
|
+
creationBlock,
|
|
3187
|
+
startingTime,
|
|
3188
|
+
lengthOfAuction,
|
|
3189
|
+
currency,
|
|
3190
|
+
minimumBid,
|
|
3191
|
+
auctionType,
|
|
3192
|
+
splitAddresses: [...splitAddresses],
|
|
3193
|
+
splitRatios: [...splitRatios],
|
|
3194
|
+
isEth: currency === ETH_ADDRESS,
|
|
3195
|
+
started,
|
|
3196
|
+
endTime,
|
|
3197
|
+
status
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
},
|
|
3201
|
+
search: {
|
|
3202
|
+
async nfts(params = {}) {
|
|
3203
|
+
const requestParams = params.chainIds ? params : { ...params, chainIds: [chainId] };
|
|
3204
|
+
return searchNfts(requestParams);
|
|
3205
|
+
},
|
|
3206
|
+
async collections(params = {}) {
|
|
3207
|
+
return searchCollections(params);
|
|
3208
|
+
}
|
|
3209
|
+
},
|
|
3210
|
+
media: {
|
|
3211
|
+
async upload(buffer, filename) {
|
|
3212
|
+
return uploadMedia(buffer, filename);
|
|
3213
|
+
},
|
|
3214
|
+
async pinMetadata(opts) {
|
|
3215
|
+
return pinMetadata(opts);
|
|
3216
|
+
}
|
|
3217
|
+
},
|
|
3218
|
+
import: {
|
|
3219
|
+
async erc721(params) {
|
|
3220
|
+
const owner = params.owner ?? config.account ?? config.walletClient?.account?.address;
|
|
3221
|
+
if (!owner) {
|
|
3222
|
+
throw new Error("No owner available for import. Pass params.owner or provide config.account/walletClient with an account.");
|
|
3223
|
+
}
|
|
3224
|
+
await importErc721({
|
|
3225
|
+
chainId,
|
|
3226
|
+
contract: params.contract,
|
|
3227
|
+
owner
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
},
|
|
3231
|
+
token: {
|
|
3232
|
+
async getContractInfo(params) {
|
|
3233
|
+
const [name, symbol, totalSupply] = await Promise.all([
|
|
3234
|
+
publicClient.readContract({
|
|
3235
|
+
address: params.contract,
|
|
3236
|
+
abi: tokenAbi,
|
|
3237
|
+
functionName: "name"
|
|
3238
|
+
}),
|
|
3239
|
+
publicClient.readContract({
|
|
3240
|
+
address: params.contract,
|
|
3241
|
+
abi: tokenAbi,
|
|
3242
|
+
functionName: "symbol"
|
|
3243
|
+
}),
|
|
3244
|
+
publicClient.readContract({
|
|
3245
|
+
address: params.contract,
|
|
3246
|
+
abi: tokenAbi,
|
|
3247
|
+
functionName: "totalSupply"
|
|
3248
|
+
})
|
|
3249
|
+
]);
|
|
3250
|
+
return {
|
|
3251
|
+
contract: params.contract,
|
|
3252
|
+
chain,
|
|
3253
|
+
name,
|
|
3254
|
+
symbol,
|
|
3255
|
+
totalSupply
|
|
3256
|
+
};
|
|
3257
|
+
},
|
|
3258
|
+
async getTokenInfo(params) {
|
|
3259
|
+
const tokenId = toInteger(params.tokenId, "tokenId");
|
|
3260
|
+
const [owner, tokenUri] = await Promise.all([
|
|
3261
|
+
publicClient.readContract({
|
|
3262
|
+
address: params.contract,
|
|
3263
|
+
abi: tokenAbi,
|
|
3264
|
+
functionName: "ownerOf",
|
|
3265
|
+
args: [tokenId]
|
|
3266
|
+
}),
|
|
3267
|
+
publicClient.readContract({
|
|
3268
|
+
address: params.contract,
|
|
3269
|
+
abi: tokenAbi,
|
|
3270
|
+
functionName: "tokenURI",
|
|
3271
|
+
args: [tokenId]
|
|
3272
|
+
})
|
|
3273
|
+
]);
|
|
3274
|
+
return {
|
|
3275
|
+
contract: params.contract,
|
|
3276
|
+
tokenId,
|
|
3277
|
+
owner,
|
|
3278
|
+
tokenUri
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
// src/commands/deploy.ts
|
|
3286
|
+
function deployErc721Command() {
|
|
3287
|
+
const cmd = new Command2("erc721");
|
|
3288
|
+
cmd.description("Deploy a new ERC-721 NFT contract via the RARE factory");
|
|
3289
|
+
cmd.argument("<name>", "name of the NFT collection").argument("<symbol>", "symbol of the NFT collection").option("--max-tokens <number>", "maximum number of tokens (optional)").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (name, symbol, opts) => {
|
|
3290
|
+
const chain = getActiveChain(opts.chain);
|
|
3291
|
+
const { client } = getWalletClient(chain);
|
|
3292
|
+
const publicClient = getPublicClient(chain);
|
|
3293
|
+
const rare = createRareClient({ publicClient, walletClient: client });
|
|
3294
|
+
console.log(`Deploying ERC-721 contract on ${chain}...`);
|
|
3295
|
+
console.log(` Factory: ${rare.contracts.factory}`);
|
|
3296
|
+
console.log(` Name: ${name}`);
|
|
3297
|
+
console.log(` Symbol: ${symbol}`);
|
|
3298
|
+
if (opts.maxTokens) console.log(` Max tokens: ${opts.maxTokens}`);
|
|
3299
|
+
console.log("Waiting for confirmation...");
|
|
3300
|
+
const result = await rare.deploy.erc721({
|
|
3301
|
+
name,
|
|
3302
|
+
symbol,
|
|
3303
|
+
maxTokens: opts.maxTokens
|
|
3304
|
+
});
|
|
3305
|
+
console.log(`Transaction sent: ${result.txHash}`);
|
|
3306
|
+
if (result.contract) {
|
|
3307
|
+
console.log(`
|
|
3308
|
+
ERC-721 contract deployed at: ${result.contract}`);
|
|
3309
|
+
} else {
|
|
3310
|
+
console.log(`
|
|
3311
|
+
Transaction confirmed. Block: ${result.receipt.blockNumber}`);
|
|
3312
|
+
console.log("Could not parse deployed address from logs.");
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
3315
|
+
return cmd;
|
|
3316
|
+
}
|
|
3317
|
+
function deployCommand() {
|
|
3318
|
+
const cmd = new Command2("deploy");
|
|
3319
|
+
cmd.description("Deploy a new contract via the RARE protocol");
|
|
3320
|
+
cmd.addCommand(deployErc721Command());
|
|
3321
|
+
return cmd;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
// src/commands/mint.ts
|
|
3325
|
+
import { Command as Command3 } from "commander";
|
|
3326
|
+
|
|
3327
|
+
// src/ipfs.ts
|
|
3328
|
+
import { basename, extname } from "path";
|
|
3329
|
+
import { readFile, stat } from "fs/promises";
|
|
3330
|
+
import { isAddress as isAddress2 } from "viem";
|
|
3331
|
+
var API_BASE_URL2 = "https://api.superrare.org";
|
|
3332
|
+
var MIME_TYPES2 = {
|
|
3333
|
+
".png": "image/png",
|
|
3334
|
+
".jpg": "image/jpeg",
|
|
3335
|
+
".jpeg": "image/jpeg",
|
|
3336
|
+
".gif": "image/gif",
|
|
3337
|
+
".webp": "image/webp",
|
|
3338
|
+
".svg": "image/svg+xml",
|
|
3339
|
+
".mp4": "video/mp4",
|
|
3340
|
+
".mov": "video/quicktime",
|
|
3341
|
+
".webm": "video/webm",
|
|
3342
|
+
".glb": "model/gltf-binary",
|
|
3343
|
+
".gltf": "model/gltf+json",
|
|
3344
|
+
".html": "text/html"
|
|
3345
|
+
};
|
|
3346
|
+
function inferMimeType2(filename) {
|
|
3347
|
+
const ext = extname(filename).toLowerCase();
|
|
3348
|
+
return MIME_TYPES2[ext] ?? "application/octet-stream";
|
|
3349
|
+
}
|
|
3350
|
+
function assertPositiveInteger2(value, fieldName) {
|
|
3351
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
3352
|
+
throw new Error(`${fieldName} must be a positive integer`);
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
function assertEvmAddress2(value, fieldName) {
|
|
3356
|
+
if (!isAddress2(value)) {
|
|
3357
|
+
throw new Error(`${fieldName} must be a valid EVM address`);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
function parseDimensions2(dimensions) {
|
|
3361
|
+
if (!dimensions) return void 0;
|
|
3362
|
+
const [w, h] = dimensions.split("x");
|
|
3363
|
+
if (!w || !h) return void 0;
|
|
3364
|
+
const width = parseInt(w, 10);
|
|
3365
|
+
const height = parseInt(h, 10);
|
|
3366
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return void 0;
|
|
3367
|
+
return { width, height };
|
|
3368
|
+
}
|
|
3369
|
+
async function apiPost2(path2, payload) {
|
|
3370
|
+
const url = `${API_BASE_URL2}${path2}`;
|
|
3371
|
+
const response = await fetch(url, {
|
|
3372
|
+
method: "POST",
|
|
3373
|
+
headers: { "Content-Type": "application/json" },
|
|
3374
|
+
body: JSON.stringify(payload)
|
|
3375
|
+
});
|
|
3376
|
+
const text = await response.text();
|
|
3377
|
+
const json = text ? JSON.parse(text) : {};
|
|
3378
|
+
if (!response.ok) {
|
|
3379
|
+
const message = json.error ?? text;
|
|
3380
|
+
throw new Error(`API error ${response.status} on ${path2}: ${message}`);
|
|
3381
|
+
}
|
|
3382
|
+
return json;
|
|
3383
|
+
}
|
|
3384
|
+
async function uploadParts2(fileBuffer, partSize, presignedUrls) {
|
|
3385
|
+
const parts = [];
|
|
3386
|
+
for (let i = 0; i < presignedUrls.length; i++) {
|
|
3387
|
+
const start = i * partSize;
|
|
3388
|
+
const end = start + partSize;
|
|
3389
|
+
const partBuffer = fileBuffer.subarray(start, end);
|
|
3390
|
+
const response = await fetch(presignedUrls[i], {
|
|
3391
|
+
method: "PUT",
|
|
3392
|
+
body: new Uint8Array(partBuffer)
|
|
3393
|
+
});
|
|
3394
|
+
if (response.status !== 200 && response.status !== 204) {
|
|
3395
|
+
throw new Error(`Part ${i + 1} upload failed with status ${response.status}`);
|
|
3396
|
+
}
|
|
3397
|
+
const etag = response.headers.get("etag");
|
|
3398
|
+
if (!etag) {
|
|
3399
|
+
throw new Error(`Missing etag header for part ${i + 1}`);
|
|
3400
|
+
}
|
|
3401
|
+
parts.push({ ETag: etag, PartNumber: i + 1 });
|
|
3402
|
+
}
|
|
3403
|
+
return parts;
|
|
3404
|
+
}
|
|
3405
|
+
async function uploadMedia2(filePath, label) {
|
|
3406
|
+
const fileStats = await stat(filePath);
|
|
3407
|
+
const fileSize = fileStats.size;
|
|
3408
|
+
const fileName = basename(filePath);
|
|
3409
|
+
const fileBuffer = await readFile(filePath);
|
|
3410
|
+
const mimeType = inferMimeType2(fileName);
|
|
3411
|
+
console.log(`Uploading ${label}: ${fileName} (${fileSize} bytes, ${mimeType})`);
|
|
3412
|
+
const init = await apiPost2("/api/nft/media-upload-url", {
|
|
3413
|
+
fileSize,
|
|
3414
|
+
filename: fileName
|
|
3415
|
+
});
|
|
3416
|
+
console.log(` Multipart upload initialized (${init.presignedUrls.length} parts)`);
|
|
3417
|
+
const parts = await uploadParts2(fileBuffer, init.partSize, init.presignedUrls);
|
|
3418
|
+
console.log(` All parts uploaded`);
|
|
3419
|
+
const complete = await apiPost2("/api/nft/media-upload-complete", {
|
|
3420
|
+
key: init.key,
|
|
3421
|
+
uploadId: init.uploadId,
|
|
3422
|
+
bucket: init.bucket,
|
|
3423
|
+
parts
|
|
3424
|
+
});
|
|
3425
|
+
console.log(` Upload complete: ${complete.ipfsUrl}`);
|
|
3426
|
+
const generated = await apiPost2("/api/nft/media-generate", {
|
|
3427
|
+
uri: complete.ipfsUrl,
|
|
3428
|
+
mimeType
|
|
3429
|
+
});
|
|
3430
|
+
const dimensions = parseDimensions2(generated.media.dimensions);
|
|
3431
|
+
const entry = {
|
|
3432
|
+
url: generated.media.uri,
|
|
3433
|
+
mimeType: generated.media.mimeType,
|
|
3434
|
+
size: generated.media.size ?? fileSize,
|
|
3435
|
+
...dimensions ? { dimensions } : {}
|
|
3436
|
+
};
|
|
3437
|
+
console.log(` Media generated: ${entry.url}`);
|
|
3438
|
+
return entry;
|
|
3439
|
+
}
|
|
3440
|
+
async function importErc7212(opts) {
|
|
3441
|
+
assertPositiveInteger2(opts.chainId, "chainId");
|
|
3442
|
+
assertEvmAddress2(opts.contractAddress, "contractAddress");
|
|
3443
|
+
assertEvmAddress2(opts.ownerAddress, "ownerAddress");
|
|
3444
|
+
const normalizedContractAddress = opts.contractAddress.toLowerCase();
|
|
3445
|
+
const normalizedOwnerAddress = opts.ownerAddress.toLowerCase();
|
|
3446
|
+
const result = await apiPost2("/api/nft/import-erc721", {
|
|
3447
|
+
chainId: opts.chainId,
|
|
3448
|
+
contractAddress: normalizedContractAddress,
|
|
3449
|
+
ownerAddress: normalizedOwnerAddress
|
|
3450
|
+
});
|
|
3451
|
+
if (result.ok !== true) {
|
|
3452
|
+
throw new Error("Unexpected response from /api/nft/import-erc721");
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
async function pinMetadata2(opts) {
|
|
3456
|
+
const nftMedia = {
|
|
3457
|
+
image: opts.image
|
|
3458
|
+
};
|
|
3459
|
+
if (opts.video) {
|
|
3460
|
+
nftMedia.video = opts.video;
|
|
3461
|
+
}
|
|
3462
|
+
const payload = {
|
|
3463
|
+
name: opts.name,
|
|
3464
|
+
description: opts.description,
|
|
3465
|
+
nftMedia,
|
|
3466
|
+
tags: opts.tags ?? []
|
|
3467
|
+
};
|
|
3468
|
+
if (opts.attributes && opts.attributes.length > 0) {
|
|
3469
|
+
payload.attributes = opts.attributes;
|
|
3470
|
+
}
|
|
3471
|
+
console.log("Pinning metadata to IPFS...");
|
|
3472
|
+
const result = await apiPost2("/api/nft/metadata", payload);
|
|
3473
|
+
console.log(`Metadata pinned: ${result.ipfsUrl}`);
|
|
3474
|
+
console.log(`Gateway URL: ${result.gatewayUrl}`);
|
|
3475
|
+
return result.ipfsUrl;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
// src/commands/mint.ts
|
|
3479
|
+
function parseAttribute(raw) {
|
|
3480
|
+
if (raw.startsWith("{")) {
|
|
3481
|
+
const parsed = JSON.parse(raw);
|
|
3482
|
+
if (parsed.value === void 0) {
|
|
3483
|
+
throw new Error(`Attribute JSON must include "value": ${raw}`);
|
|
3484
|
+
}
|
|
3485
|
+
return parsed;
|
|
3486
|
+
}
|
|
3487
|
+
const eqIndex = raw.indexOf("=");
|
|
3488
|
+
if (eqIndex === -1) {
|
|
3489
|
+
return { value: raw };
|
|
3490
|
+
}
|
|
3491
|
+
const trait_type = raw.slice(0, eqIndex);
|
|
3492
|
+
const rawValue = raw.slice(eqIndex + 1);
|
|
3493
|
+
const numValue = Number(rawValue);
|
|
3494
|
+
const value = rawValue.length > 0 && !Number.isNaN(numValue) ? numValue : rawValue;
|
|
3495
|
+
return { trait_type, value };
|
|
3496
|
+
}
|
|
3497
|
+
function mintCommand() {
|
|
3498
|
+
const cmd = new Command3("mint");
|
|
3499
|
+
cmd.description("Mint a new NFT on a deployed token contract");
|
|
3500
|
+
cmd.requiredOption("--contract <address>", "token contract address").option("--token-uri <uri>", "token metadata URI (skip upload if provided)").option("--name <name>", "NFT name").option("--description <description>", "NFT description").option("--image <path>", "path to image file").option("--video <path>", "path to video file").option("--tag <tag>", "tag (repeatable)", (val, acc) => [...acc, val], []).option("--attribute <attr>", 'attribute as "trait=value" or JSON (repeatable)', (val, acc) => [...acc, val], []).option("--to <address>", "recipient address (defaults to caller)").option("--royalty-receiver <address>", "royalty receiver address (defaults to caller)").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (opts) => {
|
|
3501
|
+
let tokenUri;
|
|
3502
|
+
if (opts.tokenUri) {
|
|
3503
|
+
tokenUri = opts.tokenUri;
|
|
3504
|
+
} else {
|
|
3505
|
+
if (!opts.name) {
|
|
3506
|
+
console.error("Error: --name is required when not using --token-uri");
|
|
3507
|
+
process.exit(1);
|
|
3508
|
+
}
|
|
3509
|
+
if (!opts.description) {
|
|
3510
|
+
console.error("Error: --description is required when not using --token-uri");
|
|
3511
|
+
process.exit(1);
|
|
3512
|
+
}
|
|
3513
|
+
if (!opts.image) {
|
|
3514
|
+
console.error("Error: --image is required when not using --token-uri");
|
|
3515
|
+
process.exit(1);
|
|
3516
|
+
}
|
|
3517
|
+
const imageMedia = await uploadMedia2(opts.image, "image");
|
|
3518
|
+
const videoMedia = opts.video ? await uploadMedia2(opts.video, "video") : void 0;
|
|
3519
|
+
const tags = opts.tag.length > 0 ? opts.tag : void 0;
|
|
3520
|
+
const attributes = opts.attribute.length > 0 ? opts.attribute.map(parseAttribute) : void 0;
|
|
3521
|
+
tokenUri = await pinMetadata2({
|
|
3522
|
+
name: opts.name,
|
|
3523
|
+
description: opts.description,
|
|
3524
|
+
image: imageMedia,
|
|
3525
|
+
video: videoMedia,
|
|
3526
|
+
tags,
|
|
3527
|
+
attributes
|
|
3528
|
+
});
|
|
3529
|
+
}
|
|
3530
|
+
const chain = getActiveChain(opts.chain);
|
|
3531
|
+
const { client, account } = getWalletClient(chain);
|
|
3532
|
+
const publicClient = getPublicClient(chain);
|
|
3533
|
+
const rare = createRareClient({ publicClient, walletClient: client });
|
|
3534
|
+
const contractAddress = opts.contract;
|
|
3535
|
+
console.log(`
|
|
3536
|
+
Minting NFT on ${chain}...`);
|
|
3537
|
+
console.log(` Contract: ${contractAddress}`);
|
|
3538
|
+
console.log(` URI: ${tokenUri}`);
|
|
3539
|
+
if (opts.to || opts.royaltyReceiver) {
|
|
3540
|
+
const receiver = opts.to ?? account.address;
|
|
3541
|
+
const royaltyReceiver = opts.royaltyReceiver ?? account.address;
|
|
3542
|
+
console.log(` To: ${receiver}`);
|
|
3543
|
+
console.log(` Royalty receiver: ${royaltyReceiver}`);
|
|
3544
|
+
}
|
|
3545
|
+
console.log("Waiting for confirmation...");
|
|
3546
|
+
const result = await rare.mint.mintTo({
|
|
3547
|
+
contract: contractAddress,
|
|
3548
|
+
tokenUri,
|
|
3549
|
+
to: opts.to,
|
|
3550
|
+
royaltyReceiver: opts.royaltyReceiver
|
|
3551
|
+
});
|
|
3552
|
+
console.log(`Transaction sent: ${result.txHash}`);
|
|
3553
|
+
if (result.tokenId !== void 0) {
|
|
3554
|
+
console.log(`
|
|
3555
|
+
NFT minted! Token ID: ${result.tokenId}`);
|
|
3556
|
+
} else {
|
|
3557
|
+
console.log(`
|
|
3558
|
+
Transaction confirmed. Block: ${result.receipt.blockNumber}`);
|
|
3559
|
+
}
|
|
3560
|
+
});
|
|
3561
|
+
return cmd;
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
// src/commands/auction.ts
|
|
3565
|
+
import { Command as Command4 } from "commander";
|
|
3566
|
+
import { formatEther } from "viem";
|
|
3567
|
+
|
|
3568
|
+
// src/errors.ts
|
|
3569
|
+
function printContractError(error) {
|
|
3570
|
+
if (!(error instanceof Error)) {
|
|
3571
|
+
console.error("\nTransaction failed:", error);
|
|
3572
|
+
process.exit(1);
|
|
3573
|
+
}
|
|
3574
|
+
console.error("\nTransaction failed:");
|
|
3575
|
+
let current = error;
|
|
3576
|
+
let depth = 0;
|
|
3577
|
+
while (current instanceof Error) {
|
|
3578
|
+
const indent = " ".repeat(depth + 1);
|
|
3579
|
+
const msg = current.shortMessage ?? current.message;
|
|
3580
|
+
console.error(`${indent}${msg}`);
|
|
3581
|
+
if ("reason" in current && current.reason) {
|
|
3582
|
+
console.error(`${indent}Revert reason: ${current.reason}`);
|
|
3583
|
+
}
|
|
3584
|
+
if ("metaMessages" in current && Array.isArray(current.metaMessages)) {
|
|
3585
|
+
for (const line of current.metaMessages) {
|
|
3586
|
+
if (line.trim()) console.error(`${indent}${line.trim()}`);
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
current = current.cause;
|
|
3590
|
+
depth++;
|
|
3591
|
+
}
|
|
3592
|
+
process.exit(1);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
// src/commands/auction.ts
|
|
3596
|
+
var ETH_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
3597
|
+
function auctionCommand() {
|
|
3598
|
+
const cmd = new Command4("auction");
|
|
3599
|
+
cmd.description("Auction subcommands (create, bid, settle, cancel, status)");
|
|
3600
|
+
cmd.command("create").description("Configure and start an auction").requiredOption("--contract <address>", "NFT contract address").requiredOption("--token-id <id>", "token ID to auction").requiredOption("--starting-price <amount>", "starting price in ETH (or token units)").requiredOption("--duration <seconds>", "auction duration in seconds").option("--currency <address>", "ERC20 currency address (defaults to ETH)").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (opts) => {
|
|
3601
|
+
const chain = getActiveChain(opts.chain);
|
|
3602
|
+
const { client } = getWalletClient(chain);
|
|
3603
|
+
const publicClient = getPublicClient(chain);
|
|
3604
|
+
const rare = createRareClient({ publicClient, walletClient: client });
|
|
3605
|
+
const currency = opts.currency ?? ETH_ADDRESS2;
|
|
3606
|
+
console.log(`Creating auction on ${chain}...`);
|
|
3607
|
+
console.log(` Auction contract: ${rare.contracts.auction}`);
|
|
3608
|
+
console.log(` NFT contract: ${opts.contract}`);
|
|
3609
|
+
console.log(` Token ID: ${opts.tokenId}`);
|
|
3610
|
+
console.log(` Starting price: ${opts.startingPrice} ETH`);
|
|
3611
|
+
console.log(` Duration: ${opts.duration} seconds`);
|
|
3612
|
+
console.log(` Currency: ${currency === ETH_ADDRESS2 ? "ETH" : currency}`);
|
|
3613
|
+
try {
|
|
3614
|
+
const result = await rare.auction.create({
|
|
3615
|
+
contract: opts.contract,
|
|
3616
|
+
tokenId: opts.tokenId,
|
|
3617
|
+
startingPrice: opts.startingPrice,
|
|
3618
|
+
duration: opts.duration,
|
|
3619
|
+
currency
|
|
3158
3620
|
});
|
|
3621
|
+
if (result.approvalTxHash) {
|
|
3622
|
+
console.log(`Approval tx sent: ${result.approvalTxHash}`);
|
|
3623
|
+
}
|
|
3624
|
+
console.log(`
|
|
3625
|
+
Transaction sent: ${result.txHash}`);
|
|
3626
|
+
console.log(`Auction created! Block: ${result.receipt.blockNumber}`);
|
|
3159
3627
|
} catch (error) {
|
|
3160
3628
|
printContractError(error);
|
|
3161
3629
|
}
|
|
3162
|
-
console.log(`
|
|
3163
|
-
Transaction sent: ${txHash}`);
|
|
3164
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3165
|
-
console.log(`Auction created! Block: ${receipt.blockNumber}`);
|
|
3166
3630
|
});
|
|
3167
3631
|
cmd.command("bid").description("Place a bid on an auction").requiredOption("--contract <address>", "NFT contract address").requiredOption("--token-id <id>", "token ID").requiredOption("--amount <amount>", "bid amount in ETH (or token units)").option("--currency <address>", "ERC20 currency address (defaults to ETH)").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (opts) => {
|
|
3168
3632
|
const chain = getActiveChain(opts.chain);
|
|
3169
|
-
const { client
|
|
3633
|
+
const { client } = getWalletClient(chain);
|
|
3170
3634
|
const publicClient = getPublicClient(chain);
|
|
3171
|
-
const
|
|
3172
|
-
const currency = opts.currency ??
|
|
3173
|
-
const isEth = currency ===
|
|
3174
|
-
const bidAmount = parseEther(opts.amount);
|
|
3635
|
+
const rare = createRareClient({ publicClient, walletClient: client });
|
|
3636
|
+
const currency = opts.currency ?? ETH_ADDRESS2;
|
|
3637
|
+
const isEth = currency === ETH_ADDRESS2;
|
|
3175
3638
|
console.log(`Placing bid on ${chain}...`);
|
|
3176
|
-
console.log(` Auction contract: ${
|
|
3639
|
+
console.log(` Auction contract: ${rare.contracts.auction}`);
|
|
3177
3640
|
console.log(` NFT contract: ${opts.contract}`);
|
|
3178
3641
|
console.log(` Token ID: ${opts.tokenId}`);
|
|
3179
3642
|
console.log(` Amount: ${opts.amount} ${isEth ? "ETH" : currency}`);
|
|
3180
|
-
let txHash;
|
|
3181
3643
|
try {
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
account,
|
|
3188
|
-
chain: void 0,
|
|
3189
|
-
value: isEth ? bidAmount : 0n
|
|
3644
|
+
const result = await rare.auction.bid({
|
|
3645
|
+
contract: opts.contract,
|
|
3646
|
+
tokenId: opts.tokenId,
|
|
3647
|
+
amount: opts.amount,
|
|
3648
|
+
currency
|
|
3190
3649
|
});
|
|
3650
|
+
console.log(`
|
|
3651
|
+
Transaction sent: ${result.txHash}`);
|
|
3652
|
+
console.log(`Bid placed! Block: ${result.receipt.blockNumber}`);
|
|
3191
3653
|
} catch (error) {
|
|
3192
3654
|
printContractError(error);
|
|
3193
3655
|
}
|
|
3194
|
-
console.log(`
|
|
3195
|
-
Transaction sent: ${txHash}`);
|
|
3196
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
3197
|
-
console.log(`Bid placed! Block: ${receipt.blockNumber}`);
|
|
3198
3656
|
});
|
|
3199
3657
|
cmd.command("settle").description("Settle a completed auction").requiredOption("--contract <address>", "NFT contract address").requiredOption("--token-id <id>", "token ID").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (opts) => {
|
|
3200
3658
|
const chain = getActiveChain(opts.chain);
|
|
3201
|
-
const { client
|
|
3659
|
+
const { client } = getWalletClient(chain);
|
|
3202
3660
|
const publicClient = getPublicClient(chain);
|
|
3203
|
-
const
|
|
3661
|
+
const rare = createRareClient({ publicClient, walletClient: client });
|
|
3204
3662
|
console.log(`Settling auction on ${chain}...`);
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
})
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
console.log(`Auction settled! Block: ${receipt.blockNumber}`);
|
|
3663
|
+
try {
|
|
3664
|
+
const result = await rare.auction.settle({
|
|
3665
|
+
contract: opts.contract,
|
|
3666
|
+
tokenId: opts.tokenId
|
|
3667
|
+
});
|
|
3668
|
+
console.log(`Transaction sent: ${result.txHash}`);
|
|
3669
|
+
console.log(`Auction settled! Block: ${result.receipt.blockNumber}`);
|
|
3670
|
+
} catch (error) {
|
|
3671
|
+
printContractError(error);
|
|
3672
|
+
}
|
|
3216
3673
|
});
|
|
3217
3674
|
cmd.command("cancel").description("Cancel an auction").requiredOption("--contract <address>", "NFT contract address").requiredOption("--token-id <id>", "token ID").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (opts) => {
|
|
3218
3675
|
const chain = getActiveChain(opts.chain);
|
|
3219
|
-
const { client
|
|
3676
|
+
const { client } = getWalletClient(chain);
|
|
3220
3677
|
const publicClient = getPublicClient(chain);
|
|
3221
|
-
const
|
|
3678
|
+
const rare = createRareClient({ publicClient, walletClient: client });
|
|
3222
3679
|
console.log(`Cancelling auction on ${chain}...`);
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
})
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
console.log(`Auction cancelled! Block: ${receipt.blockNumber}`);
|
|
3680
|
+
try {
|
|
3681
|
+
const result = await rare.auction.cancel({
|
|
3682
|
+
contract: opts.contract,
|
|
3683
|
+
tokenId: opts.tokenId
|
|
3684
|
+
});
|
|
3685
|
+
console.log(`Transaction sent: ${result.txHash}`);
|
|
3686
|
+
console.log(`Auction cancelled! Block: ${result.receipt.blockNumber}`);
|
|
3687
|
+
} catch (error) {
|
|
3688
|
+
printContractError(error);
|
|
3689
|
+
}
|
|
3234
3690
|
});
|
|
3235
3691
|
cmd.command("status").description("Get auction details (read-only)").requiredOption("--contract <address>", "NFT contract address").requiredOption("--token-id <id>", "token ID").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (opts) => {
|
|
3236
3692
|
const chain = getActiveChain(opts.chain);
|
|
3237
3693
|
const publicClient = getPublicClient(chain);
|
|
3238
|
-
const
|
|
3239
|
-
const result = await
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
functionName: "getAuctionDetails",
|
|
3243
|
-
args: [opts.contract, BigInt(opts.tokenId)]
|
|
3694
|
+
const rare = createRareClient({ publicClient });
|
|
3695
|
+
const result = await rare.auction.getStatus({
|
|
3696
|
+
contract: opts.contract,
|
|
3697
|
+
tokenId: opts.tokenId
|
|
3244
3698
|
});
|
|
3245
|
-
const
|
|
3246
|
-
const isEth = currency === ETH_ADDRESS;
|
|
3247
|
-
const started = Number(startingTime) > 0;
|
|
3248
|
-
const endTime = started ? Number(startingTime) + Number(lengthOfAuction) : null;
|
|
3249
|
-
const endDate = endTime ? new Date(endTime * 1e3) : null;
|
|
3699
|
+
const endDate = result.endTime ? new Date(Number(result.endTime) * 1e3) : null;
|
|
3250
3700
|
console.log("\nAuction Details:");
|
|
3251
|
-
console.log(` Seller: ${seller}`);
|
|
3252
|
-
console.log(` Minimum bid: ${formatEther(minimumBid)} ${isEth ? "ETH" : currency}`);
|
|
3253
|
-
console.log(` Currency: ${isEth ? "ETH" : currency}`);
|
|
3254
|
-
console.log(` Duration: ${lengthOfAuction}s`);
|
|
3255
|
-
console.log(` Status: ${
|
|
3256
|
-
if (started) {
|
|
3257
|
-
console.log(` Started at: ${new Date(Number(startingTime) * 1e3).toISOString()}`);
|
|
3701
|
+
console.log(` Seller: ${result.seller}`);
|
|
3702
|
+
console.log(` Minimum bid: ${formatEther(result.minimumBid)} ${result.isEth ? "ETH" : result.currency}`);
|
|
3703
|
+
console.log(` Currency: ${result.isEth ? "ETH" : result.currency}`);
|
|
3704
|
+
console.log(` Duration: ${result.lengthOfAuction}s`);
|
|
3705
|
+
console.log(` Status: ${result.status}`);
|
|
3706
|
+
if (result.started) {
|
|
3707
|
+
console.log(` Started at: ${new Date(Number(result.startingTime) * 1e3).toISOString()}`);
|
|
3258
3708
|
console.log(` Ends at: ${endDate.toISOString()}`);
|
|
3259
3709
|
}
|
|
3260
|
-
console.log(` Creation block: ${creationBlock}`);
|
|
3261
|
-
console.log(` Auction type: ${auctionType}`);
|
|
3710
|
+
console.log(` Creation block: ${result.creationBlock}`);
|
|
3711
|
+
console.log(` Auction type: ${result.auctionType}`);
|
|
3262
3712
|
});
|
|
3263
3713
|
return cmd;
|
|
3264
3714
|
}
|
|
@@ -3271,51 +3721,25 @@ function statusCommand() {
|
|
|
3271
3721
|
cmd.requiredOption("--contract <address>", "token contract address").option("--token-id <id>", "token ID to query (optional)").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").action(async (opts) => {
|
|
3272
3722
|
const chain = getActiveChain(opts.chain);
|
|
3273
3723
|
const publicClient = getPublicClient(chain);
|
|
3724
|
+
const rare = createRareClient({ publicClient });
|
|
3274
3725
|
const contractAddress = opts.contract;
|
|
3275
|
-
const
|
|
3276
|
-
publicClient.readContract({
|
|
3277
|
-
address: contractAddress,
|
|
3278
|
-
abi: tokenAbi,
|
|
3279
|
-
functionName: "name"
|
|
3280
|
-
}),
|
|
3281
|
-
publicClient.readContract({
|
|
3282
|
-
address: contractAddress,
|
|
3283
|
-
abi: tokenAbi,
|
|
3284
|
-
functionName: "symbol"
|
|
3285
|
-
}),
|
|
3286
|
-
publicClient.readContract({
|
|
3287
|
-
address: contractAddress,
|
|
3288
|
-
abi: tokenAbi,
|
|
3289
|
-
functionName: "totalSupply"
|
|
3290
|
-
})
|
|
3291
|
-
]);
|
|
3726
|
+
const contractInfo = await rare.token.getContractInfo({ contract: contractAddress });
|
|
3292
3727
|
console.log("\nContract Info:");
|
|
3293
|
-
console.log(` Address: ${
|
|
3294
|
-
console.log(` Chain: ${chain}`);
|
|
3295
|
-
console.log(` Name: ${name}`);
|
|
3296
|
-
console.log(` Symbol: ${symbol}`);
|
|
3297
|
-
console.log(` Total Supply: ${totalSupply}`);
|
|
3728
|
+
console.log(` Address: ${contractInfo.contract}`);
|
|
3729
|
+
console.log(` Chain: ${contractInfo.chain}`);
|
|
3730
|
+
console.log(` Name: ${contractInfo.name}`);
|
|
3731
|
+
console.log(` Symbol: ${contractInfo.symbol}`);
|
|
3732
|
+
console.log(` Total Supply: ${contractInfo.totalSupply}`);
|
|
3298
3733
|
if (opts.tokenId !== void 0) {
|
|
3299
|
-
const tokenId = BigInt(opts.tokenId);
|
|
3300
3734
|
try {
|
|
3301
|
-
const
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
functionName: "ownerOf",
|
|
3306
|
-
args: [tokenId]
|
|
3307
|
-
}),
|
|
3308
|
-
publicClient.readContract({
|
|
3309
|
-
address: contractAddress,
|
|
3310
|
-
abi: tokenAbi,
|
|
3311
|
-
functionName: "tokenURI",
|
|
3312
|
-
args: [tokenId]
|
|
3313
|
-
})
|
|
3314
|
-
]);
|
|
3735
|
+
const tokenInfo = await rare.token.getTokenInfo({
|
|
3736
|
+
contract: contractAddress,
|
|
3737
|
+
tokenId: opts.tokenId
|
|
3738
|
+
});
|
|
3315
3739
|
console.log(`
|
|
3316
|
-
Token #${
|
|
3317
|
-
console.log(` Owner: ${owner}`);
|
|
3318
|
-
console.log(` URI: ${
|
|
3740
|
+
Token #${tokenInfo.tokenId}:`);
|
|
3741
|
+
console.log(` Owner: ${tokenInfo.owner}`);
|
|
3742
|
+
console.log(` URI: ${tokenInfo.tokenUri}`);
|
|
3319
3743
|
} catch (err) {
|
|
3320
3744
|
console.log(`
|
|
3321
3745
|
Token #${opts.tokenId}: not found or error reading token`);
|
|
@@ -3369,9 +3793,9 @@ Private key saved to config for chain: ${chain}`);
|
|
|
3369
3793
|
import { Command as Command7 } from "commander";
|
|
3370
3794
|
|
|
3371
3795
|
// src/search.ts
|
|
3372
|
-
var
|
|
3373
|
-
async function
|
|
3374
|
-
const url = `${
|
|
3796
|
+
var API_BASE_URL3 = "https://api.superrare.org";
|
|
3797
|
+
async function searchPost2(path2, payload) {
|
|
3798
|
+
const url = `${API_BASE_URL3}${path2}`;
|
|
3375
3799
|
const response = await fetch(url, {
|
|
3376
3800
|
method: "POST",
|
|
3377
3801
|
headers: { "Content-Type": "application/json" },
|
|
@@ -3385,8 +3809,8 @@ async function searchPost(path2, payload) {
|
|
|
3385
3809
|
}
|
|
3386
3810
|
return json;
|
|
3387
3811
|
}
|
|
3388
|
-
async function
|
|
3389
|
-
return
|
|
3812
|
+
async function searchNfts2(params) {
|
|
3813
|
+
return searchPost2("/api/search/nfts", {
|
|
3390
3814
|
query: params.query ?? "",
|
|
3391
3815
|
take: params.take ?? 24,
|
|
3392
3816
|
cursor: params.cursor ?? 0,
|
|
@@ -3399,8 +3823,8 @@ async function searchNfts(params) {
|
|
|
3399
3823
|
...params.chainIds ? { chainIds: params.chainIds } : {}
|
|
3400
3824
|
});
|
|
3401
3825
|
}
|
|
3402
|
-
async function
|
|
3403
|
-
return
|
|
3826
|
+
async function searchCollections2(params) {
|
|
3827
|
+
return searchPost2("/api/search/collections", {
|
|
3404
3828
|
query: params.query ?? "",
|
|
3405
3829
|
take: params.take ?? 24,
|
|
3406
3830
|
cursor: params.cursor ?? 0,
|
|
@@ -3449,7 +3873,7 @@ function searchCommand() {
|
|
|
3449
3873
|
const ownerAddresses = opts.mine ? [getWalletAddress(chain)] : opts.owner ? [opts.owner] : [];
|
|
3450
3874
|
const label = opts.mine ? `NFTs owned by ${ownerAddresses[0]}` : opts.owner ? `NFTs owned by ${opts.owner}` : "NFTs";
|
|
3451
3875
|
console.log(`Searching ${label} on ${chain}...`);
|
|
3452
|
-
const page = await
|
|
3876
|
+
const page = await searchNfts2({
|
|
3453
3877
|
query: opts.query,
|
|
3454
3878
|
take: parseInt(opts.take, 10),
|
|
3455
3879
|
cursor: parseInt(opts.cursor, 10),
|
|
@@ -3461,7 +3885,7 @@ function searchCommand() {
|
|
|
3461
3885
|
cmd.command("auctions").description("List NFTs with active or configured auctions").option("--chain <chain>", "chain to use (mainnet, sepolia, base, base-sepolia)").option("--state <states...>", "auction states to filter (PENDING, RUNNING, SETTLED, UNSETTLED)", ["PENDING", "RUNNING"]).option("--owner <address>", "filter by owner address (optional)").option("--query <text>", "text search query", "").option("--take <n>", "number of results per page", "24").option("--cursor <n>", "pagination cursor", "0").action(async (opts) => {
|
|
3462
3886
|
const chain = getActiveChain(opts.chain);
|
|
3463
3887
|
console.log(`Searching auctions (${opts.state.join(", ")}) on ${chain}...`);
|
|
3464
|
-
const page = await
|
|
3888
|
+
const page = await searchNfts2({
|
|
3465
3889
|
query: opts.query,
|
|
3466
3890
|
take: parseInt(opts.take, 10),
|
|
3467
3891
|
cursor: parseInt(opts.cursor, 10),
|
|
@@ -3475,7 +3899,7 @@ function searchCommand() {
|
|
|
3475
3899
|
const chain = getActiveChain(opts.chain);
|
|
3476
3900
|
const address = getWalletAddress(chain);
|
|
3477
3901
|
console.log(`Searching collections owned by ${address}...`);
|
|
3478
|
-
const page = await
|
|
3902
|
+
const page = await searchCollections2({
|
|
3479
3903
|
query: opts.query,
|
|
3480
3904
|
take: parseInt(opts.take, 10),
|
|
3481
3905
|
cursor: parseInt(opts.cursor, 10),
|
|
@@ -3503,7 +3927,7 @@ function listCollectionsCommand() {
|
|
|
3503
3927
|
let cursor = 0;
|
|
3504
3928
|
let hasMore = true;
|
|
3505
3929
|
while (hasMore) {
|
|
3506
|
-
const page = await
|
|
3930
|
+
const page = await searchCollections2({
|
|
3507
3931
|
query: opts.query,
|
|
3508
3932
|
take: 100,
|
|
3509
3933
|
cursor,
|
|
@@ -3546,7 +3970,7 @@ function importCommand() {
|
|
|
3546
3970
|
console.log(` Chain: ${chain} (${chainId})`);
|
|
3547
3971
|
console.log(` Contract: ${contractAddress}`);
|
|
3548
3972
|
console.log(` Owner: ${ownerAddress}`);
|
|
3549
|
-
await
|
|
3973
|
+
await importErc7212({
|
|
3550
3974
|
chainId,
|
|
3551
3975
|
contractAddress,
|
|
3552
3976
|
ownerAddress
|
|
@@ -3559,7 +3983,7 @@ Contract imported successfully.`);
|
|
|
3559
3983
|
|
|
3560
3984
|
// src/index.ts
|
|
3561
3985
|
var program = new Command10();
|
|
3562
|
-
program.name("rare").description("CLI tool for interacting with the RARE protocol smart contracts").version("0.
|
|
3986
|
+
program.name("rare").description("CLI tool for interacting with the RARE protocol smart contracts").version("0.3.0");
|
|
3563
3987
|
program.addCommand(configureCommand());
|
|
3564
3988
|
program.addCommand(deployCommand());
|
|
3565
3989
|
program.addCommand(mintCommand());
|