Skip to content

Commit

Permalink
Merge pull request #9 from multiversx/custom-address-hrps
Browse files Browse the repository at this point in the history
Custom Address HRPs
  • Loading branch information
michavie authored Oct 15, 2024
2 parents 651763e + a4ddec2 commit b573e20
Show file tree
Hide file tree
Showing 38 changed files with 452 additions and 204 deletions.
85 changes: 72 additions & 13 deletions src/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,55 @@
use function BitWasp\Bech32\decode;
use function BitWasp\Bech32\encode;
use InvalidArgumentException;
use Throwable;

class Address
{
const HRP = 'erd';
const DEFAULT_HRP = 'erd';
const CONTRACT_HEX_PUBKEY_PREFIX = '0000000000000000';
const BECH32_ADDRESS_LENGTH = 62;
const PUBKEY_LENGTH = 32;

private function __construct(
private string $valueHex,
public readonly string $hrp = self::DEFAULT_HRP
) {
}

public static function fromHex(string $value): Address
public static function newFromHex(string $value, string $hrp = self::DEFAULT_HRP): Address
{
if (! self::isValidHex($value)) {
throw new InvalidArgumentException('invalid hex value');
}

return new Address(
$value ?: throw new InvalidArgumentException('hex value is required')
$value ?: throw new InvalidArgumentException('hex value is required'),
$hrp
);
}

public static function fromBech32(string $address): Address
public static function newFromBech32(string $address): Address
{
if (strlen($address) !== self::BECH32_ADDRESS_LENGTH) {
throw new Exception('invalid address length');
}

$decoded = decode(strtolower($address))[1];
[$hrp, $decoded] = decode(strtolower($address));
$res = convertBits($decoded, count($decoded), 5, 8, false);
$pieces = array_map(fn ($bits) => dechex($bits), $res);
$hex = array_reduce($pieces, fn ($carry, $hex) => $carry . str_pad($hex, 2, "0", STR_PAD_LEFT));

return new Address($hex);
return new Address($hex, $hrp);
}

public static function fromBase64(string $value): Address
public static function newFromBase64(string $value, string $hrp = self::DEFAULT_HRP): Address
{
return new Address(bin2hex(base64_decode($value)));
return new Address(bin2hex(base64_decode($value)), $hrp);
}

public static function zero(): Address
public static function zero(string $hrp = self::DEFAULT_HRP): Address
{
return new Address(str_repeat('0', 64));
return new Address(str_repeat('0', 64), $hrp);
}

public function hex(): string
Expand All @@ -60,22 +68,30 @@ public function bech32(): string
$bin = hex2bin($this->valueHex);
$bits = array_values(unpack('C*', $bin));

return encode(self::HRP, convertBits($bits, count($bits), 8, 5));
return encode($this->hrp, convertBits($bits, count($bits), 8, 5));
}

public function getPublicKey(): PublicKey
{
return new PublicKey($this->valueHex);
}

public function isContractAddress(): bool
public function isSmartContract(): bool
{
return str_starts_with($this->valueHex, self::CONTRACT_HEX_PUBKEY_PREFIX);
}

/**
* @deprecated Use {@link isSmartContract} instead.
*/
public function isContractAddress(): bool
{
return $this->isSmartContract();
}

public function isZero(): bool
{
return $this->valueHex === Address::zero()->valueHex;
return $this->valueHex === Address::zero($this->hrp)->valueHex;
}

public function equals(?Address $other): bool
Expand All @@ -89,4 +105,47 @@ public function __toString()
{
return $this->bech32();
}

private static function isValidHex(string $value): bool
{
return ctype_xdigit($value) && strlen($value) === 64;
}

public static function isValid(string $address): bool
{
try {
$decoded = decode($address);

[$hrp, $data] = $decoded;
$pubkey = convertBits($data, count($data), 5, 8, false);

return $hrp === self::DEFAULT_HRP && count($pubkey) === self::PUBKEY_LENGTH;
} catch (Throwable $th) {
return false;
}
}

/**
* @deprecated Use {@link newFromHex} instead.
*/
public static function fromHex(string $value, string $hrp = self::DEFAULT_HRP): Address
{
return self::newFromHex($value, $hrp);
}

/**
* @deprecated Use {@link newFromBech32} instead.
*/
public static function fromBech32(string $address): Address
{
return self::newFromBech32($address);
}

/**
* @deprecated Use {@link newFromBase64} instead.
*/
public static function fromBase64(string $value, string $hrp = self::DEFAULT_HRP): Address
{
return self::newFromBase64($value, $hrp);
}
}
2 changes: 1 addition & 1 deletion src/Http/Entities/Account.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function __construct(
protected static function transformResponse(array $res): array
{
return array_merge($res, [
'address' => Address::fromBech32($res['address']),
'address' => Address::newFromBech32($res['address']),
'balance' => BigInteger::of($res['balance'] ?? 0)
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/CollectionAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function __construct(
public static function transformResponse(array $res): array
{
return array_merge($res, [
'address' => Address::fromBech32($res['address']),
'address' => Address::newFromBech32($res['address']),
'balance' => BigInteger::of($res['balance'] ?? 1),
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/Nft.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ protected static function transformResponse(array $res): array
{
return array_merge($res, [
'description' => $res['metadata']['description'] ?? null,
'owner' => isset($res['owner']) ? Address::fromBech32($res['owner']) : null,
'owner' => isset($res['owner']) ? Address::newFromBech32($res['owner']) : null,
'supply' => isset($res['supply']) ? BigInteger::of($res['supply']) : null,
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/NftCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function __construct(
protected static function transformResponse(array $res): array
{
return array_merge($res, [
'owner' => Address::fromBech32($res['owner']),
'owner' => Address::newFromBech32($res['owner']),
'roles' => isset($res['roles']) ? CollectionRoles::fromArrayMultiple($res['roles']) : collect(),
'assets' => isset($res['assets']) ? TokenAssets::fromArray($res['assets']) : null,
]);
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/NftCollectionAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function __construct(
protected static function transformResponse(array $res): array
{
return array_merge($res, [
'owner' => isset($res['owner']) ? Address::fromBech32($res['owner']) : null,
'owner' => isset($res['owner']) ? Address::newFromBech32($res['owner']) : null,
'roles' => isset($res['roles']) ? CollectionRoles::fromArrayMultiple($res['roles']) : collect(),
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/NftCollectionRole.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function __construct(
protected static function transformResponse(array $res): array
{
return array_merge($res, [
'owner' => isset($res['owner']) ? Address::fromBech32($res['owner']) : null,
'owner' => isset($res['owner']) ? Address::newFromBech32($res['owner']) : null,
'roles' => isset($res['roles']) ? CollectionRoles::fromArrayMultiple($res['roles']) : collect(),
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/NftOwner.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __construct(
protected static function transformResponse(array $res): array
{
return array_merge($res, [
'address' => Address::fromBech32($res['address']),
'address' => Address::newFromBech32($res['address']),
'balance' => (int) $res['balance'],
]);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Http/Entities/SmartContractResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ protected static function transformResponse(array $res): array
'timestamp' => isset($res['timestamp']) ? Carbon::createFromTimestampUTC($res['timestamp']) : null,
'data' => isset($res['data']) ? base64_decode($res['data']) : null,
'value' => isset($res['value']) ? BigInteger::of($res['value']) : null,
'sender' => isset($res['sender']) ? Address::fromBech32($res['sender']) : null,
'receiver' => isset($res['receiver']) ? Address::fromBech32($res['receiver']) : null,
'sender' => isset($res['sender']) ? Address::newFromBech32($res['sender']) : null,
'receiver' => isset($res['receiver']) ? Address::newFromBech32($res['receiver']) : null,
'logs' => isset($res['logs']['id']) ? TransactionLog::fromArray($res['logs']) : null,
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/TokenAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function __construct(
public static function transformResponse(array $res): array
{
return array_merge($res, [
'address' => Address::fromBech32($res['address']),
'address' => Address::newFromBech32($res['address']),
'balance' => BigInteger::of($res['balance'] ?? 0),
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/TokenAddressRoles.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __construct(
public static function transformResponse(array $res): array
{
return array_merge($res, [
'address' => Address::fromBech32($res['address']),
'address' => Address::newFromBech32($res['address']),
]);
}
}
2 changes: 1 addition & 1 deletion src/Http/Entities/TokenDetailed.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function __construct(
public static function transformResponse(array $res): array
{
return array_merge($res, [
'owner' => Address::fromBech32($res['owner']),
'owner' => Address::newFromBech32($res['owner']),
'assets' => isset($res['assets']) ? TokenAssets::fromArray($res['assets']) : null,
]);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Http/Entities/TransactionDetailed.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ protected static function transformResponse(array $res): array
{
return array_merge($res, [
'value' => isset($res['value']) ? BigInteger::of($res['value']) : BigInteger::zero(),
'sender' => isset($res['sender']) ? Address::fromBech32($res['sender']) : null,
'receiver' => isset($res['receiver']) ? Address::fromBech32($res['receiver']) : null,
'sender' => isset($res['sender']) ? Address::newFromBech32($res['sender']) : null,
'receiver' => isset($res['receiver']) ? Address::newFromBech32($res['receiver']) : null,
'data' => isset($res['data']) ? base64_decode($res['data']) : null,
'timestamp' => isset($res['timestamp']) ? Carbon::createFromTimestampUTC($res['timestamp']) : null,
'results' => isset($res['results']) ? SmartContractResult::fromArrayMultiple($res['results']) : collect(),
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/TransactionLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function __construct(
protected static function transformResponse(array $res): array
{
return array_merge($res, [
'address' => isset($res['address']) ? Address::fromBech32($res['address']) : null,
'address' => isset($res['address']) ? Address::newFromBech32($res['address']) : null,
'events' => isset($res['events']) ? TransactionLogEvent::fromArrayMultiple($res['events']) : collect(),
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Entities/TransactionLogEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function __construct(
protected static function transformResponse(array $res): array
{
return array_merge($res, [
'address' => isset($res['address']) ? Address::fromBech32($res['address']) : null,
'address' => isset($res['address']) ? Address::newFromBech32($res['address']) : null,
'topics' => isset($res['topics']) ? collect($res['topics']) : [],
]);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Http/Entities/TransactionOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ protected static function transformResponse(array $res): array
{
return array_merge($res, [
'value' => isset($res['value']) ? BigInteger::of($res['value']) : BigInteger::zero(),
'sender' => isset($res['sender']) ? Address::fromBech32($res['sender']) : null,
'receiver' => isset($res['receiver']) ? Address::fromBech32($res['receiver']) : null,
'sender' => isset($res['sender']) ? Address::newFromBech32($res['sender']) : null,
'receiver' => isset($res['receiver']) ? Address::newFromBech32($res['receiver']) : null,
]);
}
}
4 changes: 2 additions & 2 deletions src/Http/Entities/TransactionSendResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public function __construct(
protected static function transformResponse(array $res): array
{
return array_merge($res, [
'receiver' => Address::fromBech32($res['receiver']),
'sender' => Address::fromBech32($res['sender']),
'receiver' => Address::newFromBech32($res['receiver']),
'sender' => Address::newFromBech32($res['sender']),
]);
}
}
2 changes: 1 addition & 1 deletion src/TransactionPayload.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public static function setNftRoles(string $collection, string $address, array $r
{
$data = collect(['setSpecialRole'])
->push(bin2hex($collection))
->push(Address::fromBech32($address)->hex())
->push(Address::newFromBech32($address)->hex())
->push(...collect($roles)
->map(fn (string $role) => Encoder::toHex($role))
->all())
Expand Down
4 changes: 2 additions & 2 deletions src/Utils/Encoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ class Encoder
{
public static function toHex(string|int|BigInteger|Address $value): string
{
if (is_string($value) && str_starts_with($value, Address::HRP) && strlen($value) === Address::BECH32_ADDRESS_LENGTH) {
return Address::fromBech32($value)->hex();
if (is_string($value) && strlen($value) === Address::BECH32_ADDRESS_LENGTH && Address::isValid($value)) {
return Address::newFromBech32($value)->hex();
}

if (is_string($value)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"address": {},
"address": {
"hrp": "erd"
},
"nonce": 404,
"balance": "46931489907311278839",
"txCount": 470,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"type": "NonFungibleESDT",
"name": "EvolutionSeries",
"ticker": "EVOLUTIONS-3a6ba5",
"owner": {},
"owner": {
"hrp": "erd"
},
"canFreeze": false,
"canWipe": false,
"canPause": false,
Expand Down
Loading

0 comments on commit b573e20

Please sign in to comment.