fixed ipfs image decoding

This commit is contained in:
viraladmin 2026-07-30 10:39:29 -06:00
parent ea83f8eb0a
commit 956f788218
3 changed files with 112 additions and 9 deletions

View File

@ -9,6 +9,7 @@ use Contractless\Api\Http\JsonResponse;
use Contractless\Api\Http\Request; use Contractless\Api\Http\Request;
use Contractless\Api\Http\Router; use Contractless\Api\Http\Router;
use Contractless\Api\Nft\NftMediaService; use Contractless\Api\Nft\NftMediaService;
use Contractless\Api\Rpc\RpcReplyDecoder;
final class NftMediaRoutes final class NftMediaRoutes
{ {
@ -52,12 +53,10 @@ final class NftMediaRoutes
); );
} }
$series = Request::queryInt('series', 0, 0, 4_294_967_295); $series = Request::queryInt('series', 0, 0, 4_294_967_295);
$reply = $application->client->nftDetails($name, $series); $details = RpcReplyDecoder::nftDetails(
\Contractless\Api\Rpc\RpcReplyDecoder::notNodeError($reply); $application->client->nftDetails($name, $series),
if (strlen($reply) < 173) { );
throw new \RuntimeException('The node returned invalid NFT details.'); $cid = $details['item_ipfs'];
}
$cid = trim(substr($reply, 73, 100), "\0 ");
return [$name, $series, $cid]; return [$name, $series, $cid];
} }
} }

View File

@ -222,10 +222,8 @@ final class RemainingRoutes
$name = self::assetName(Request::queryString('name', 15)); $name = self::assetName(Request::queryString('name', 15));
$series = Request::queryInt('series', 0, 0, 4_294_967_295); $series = Request::queryInt('series', 0, 0, 4_294_967_295);
JsonResponse::success( JsonResponse::success(
['name' => $name, 'series' => $series] RpcReplyDecoder::nftDetails(
+ RpcReplyDecoder::jsonTextOrRaw(
$application->client->nftDetails($name, $series), $application->client->nftDetails($name, $series),
'NFT details',
), ),
); );
}); });

View File

@ -232,6 +232,112 @@ final class RpcReplyDecoder
return $nfts; return $nfts;
} }
/**
* @return array{
* nft_name: string,
* series: int,
* asset_name: string,
* genesis_txid: string,
* creator: string,
* item_ipfs: string,
* metadata_uri: string,
* current_holder: string|null,
* history_count: int,
* history: list<array<string, mixed>>
* }
*/
public static function nftDetails(string $reply): array
{
self::notNodeError($reply);
$fixedBytes = 15 + 4 + 32 + 22 + 100 + 22 + 4;
$historyBytes = 109;
if (strlen($reply) < $fixedBytes) {
throw new RuntimeException('The node returned truncated NFT details.');
}
$offset = 0;
$name = rtrim(substr($reply, $offset, 15), "\0 ");
$offset += 15;
$series = self::u32($reply, $offset);
$genesisTxid = bin2hex(substr($reply, $offset, 32));
$offset += 32;
$creator = self::walletAddress(substr($reply, $offset, 22));
$offset += 22;
$cid = rtrim(substr($reply, $offset, 100), "\0 ");
$offset += 100;
$holderBytes = substr($reply, $offset, 22);
$holder = $holderBytes === str_repeat("\0", 22)
? null
: self::walletAddress($holderBytes);
$offset += 22;
$historyCount = self::u32($reply, $offset);
$expected = $fixedBytes + ($historyCount * $historyBytes);
if (strlen($reply) !== $expected) {
throw new RuntimeException('The node returned invalid NFT history data.');
}
$actions = [
1 => 'Create',
2 => 'Transfer',
3 => 'Swap',
4 => 'Loan Locked',
5 => 'Loan Payment',
6 => 'Collateral Claimed',
7 => 'Loan Issued',
8 => 'Burned',
];
$history = [];
for ($index = 0; $index < $historyCount; $index++) {
$txid = bin2hex(substr($reply, $offset, 32));
$offset += 32;
$block = self::u32($reply, $offset);
$transactionType = ord($reply[$offset++]);
$actionType = ord($reply[$offset++]);
$fromBytes = substr($reply, $offset, 22);
$offset += 22;
$toBytes = substr($reply, $offset, 22);
$offset += 22;
$receivedAsset = rtrim(substr($reply, $offset, 15), "\0 ");
$offset += 15;
$receivedSeries = self::u32($reply, $offset);
$receivedValue = self::u64($reply, $offset);
$history[] = [
'txid' => $txid,
'block' => $block,
'transaction_type' => $transactionType,
'action' => $actions[$actionType] ?? 'Unknown',
'from' => $fromBytes === str_repeat("\0", 22)
? null
: self::walletAddress($fromBytes),
'to' => $toBytes === str_repeat("\0", 22)
? null
: self::walletAddress($toBytes),
'received_asset' => $receivedAsset,
'received_series' => $receivedSeries,
'received_value_atomic' => $receivedValue,
'received_value' => self::decimal($receivedValue),
];
}
$metadataUri = $series > 0 && !str_ends_with($cid, '.json')
? 'ipfs://' . rtrim($cid, '/') . '/metadata/' . $series . '.json'
: 'ipfs://' . $cid;
return [
'nft_name' => $name,
'series' => $series,
'asset_name' => $series > 0 ? $name . '_' . $series : $name,
'genesis_txid' => $genesisTxid,
'creator' => $creator,
'item_ipfs' => $cid,
'metadata_uri' => $metadataUri,
'current_holder' => $holder,
'history_count' => $historyCount,
'history' => $history,
];
}
/** /**
* @return array{ * @return array{
* total_bytes: int, * total_bytes: int,