Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/dart/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ All notable changes to this package are documented here. Format based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[SemVer](https://semver.org/spec/v2.0.0.html).

## 0.2.1-beta
## 0.1.1-beta

### Changed

- Pin the deployed SafHandle mainnet contract address on `safrochain-1`.
- Point the mainnet preset at `rpc/api.safrochain.network` and set the
gas price to `0.15usaf`.

## 0.1.0

### Added

Expand Down
2 changes: 1 addition & 1 deletion packages/dart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ rules, and message shapes match one-to-one.
```yaml
# pubspec.yaml
dependencies:
safhandle: ^0.2.1-beta
safhandle: ^0.1.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Sync the install snippet with the release version.

pubspec.yaml now publishes 0.1.1-beta, but the README still tells users to install ^0.1.0. That leaves the published docs pointing at the wrong release line.

Suggested fix
-  safhandle: ^0.1.0
+  safhandle: ^0.1.1-beta
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
safhandle: ^0.1.0
safhandle: ^0.1.1-beta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dart/README.md` at line 26, The README install snippet is out of
sync with the published Dart release line, so update the dependency version
shown under the install example to match the current package version used in
pubspec.yaml. Adjust the dependency entry in the README so users are directed to
the same release line as the published package, keeping the docs aligned with
the package’s actual version.

```

```bash
Expand Down
3 changes: 2 additions & 1 deletion packages/dart/example/resolve.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ Future<void> main(List<String> args) async {

final record = await client.getAddress(input);
stdout.writeln('$input → $address');
stdout.writeln(' type: ${record.recordType}, key: ${record.normalizedKey}');
stdout
.writeln(' type: ${record.recordType}, key: ${record.normalizedKey}');
} finally {
client.close();
}
Expand Down
12 changes: 8 additions & 4 deletions packages/dart/lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ class SafHandleClient {

late final http.Response res;
try {
res = await _http.get(uri, headers: const <String, String>{'Accept': 'application/json'});
res = await _http.get(uri,
headers: const <String, String>{'Accept': 'application/json'});
} catch (err) {
throw SafHandleQueryException('Request failed: $err');
}
Expand Down Expand Up @@ -127,7 +128,8 @@ class SafHandleClient {
/// Throws when the handle is unregistered (see [lookup] for a null-returning
/// variant).
Future<GetAddressResponse> getAddress(String input) async {
return GetAddressResponse.fromJson(await _queryObject(QueryMsg.getAddress(input)));
return GetAddressResponse.fromJson(
await _queryObject(QueryMsg.getAddress(input)));
}

/// Resolve a name, returning just the address, or `null` if the handle is not
Expand All @@ -144,15 +146,17 @@ class SafHandleClient {

/// Resolve by name only. [name] may be bare (`john`) or full (`john.saf`).
Future<String> resolveName(String name) async {
final res = AddressResponse.fromJson(await _queryObject(QueryMsg.resolveName(name)));
final res = AddressResponse.fromJson(
await _queryObject(QueryMsg.resolveName(name)));
return res.address;
}

/// Reverse lookup: the name owned by an address. Never throws on a missing
/// record — [HandlesResponse.name] comes back as `null` when the address owns
/// none.
Future<HandlesResponse> getHandles(String address) async {
return HandlesResponse.fromJson(await _queryObject(QueryMsg.handles(address)));
return HandlesResponse.fromJson(
await _queryObject(QueryMsg.handles(address)));
}

/// Full record for a name, including owner and registration height.
Expand Down
22 changes: 11 additions & 11 deletions packages/dart/lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ class NetworkConfig {

/// Deployed SafHandle contract address per named network, baked into the SDK so
/// callers don't have to carry it around.
///
/// `mainnet` is a placeholder — swap it for the real address once the contract
/// is deployed and the SDK is verified against it.
const Map<SafHandleNetwork, String> contractAddresses = <SafHandleNetwork, String>{
const Map<SafHandleNetwork, String> contractAddresses =
<SafHandleNetwork, String>{
SafHandleNetwork.testnet:
'addr_safro17ykz5k26fzg0808knarpgg2tdxxxfmefuh9mydusqyfkr3vtdvysvv2vs0',
SafHandleNetwork.mainnet: 'addr_saf_MAIN_NET',
SafHandleNetwork.mainnet:
'addr_safro14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0nv26n',
};

/// Safrochain testnet (`safro-testnet-1`) preset.
Expand All @@ -65,16 +64,17 @@ const NetworkConfig safrochainTestnet = NetworkConfig(
'addr_safro17ykz5k26fzg0808knarpgg2tdxxxfmefuh9mydusqyfkr3vtdvysvv2vs0',
);

/// Safrochain mainnet (`safrochain-1`) preset. Contract address is a placeholder
/// until the contract is deployed.
/// Safrochain mainnet (`safrochain-1`) preset.
const NetworkConfig safrochainMainnet = NetworkConfig(
chainId: 'safrochain-1',
rpcEndpoint: 'https://rpc.safrochain.com',
restEndpoint: 'https://api.safrochain.com',
rpcEndpoint: 'https://rpc.safrochain.network',
restEndpoint: 'https://api.safrochain.network',
addressPrefix: 'addr_safro',
denom: 'usaf',
gasPrice: '100000usaf',
contractAddress: 'addr_saf_MAIN_NET',
gasPrice: '0.15usaf',
// SafHandle v1 (name-only) contract on safrochain-1.
contractAddress:
'addr_safro14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0nv26n',
);

/// Default gas price used when constructing a signing client.
Expand Down
6 changes: 4 additions & 2 deletions packages/dart/lib/src/signing_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import 'validation.dart';
/// Result of a signed, broadcast execute call.
@immutable
class ExecuteResult {
const ExecuteResult({required this.transactionHash, this.height, this.rawLog});
const ExecuteResult(
{required this.transactionHash, this.height, this.rawLog});

final String transactionHash;
final int? height;
Expand Down Expand Up @@ -117,7 +118,8 @@ class SafHandleSigningClient extends SafHandleClient {
/// Register a short name for the sender. Validates and normalizes locally,
/// attaches the exact registration fee (from [WriteOptions.fee], else fetched
/// from the contract config).
Future<ExecuteResult> registerName(String name, {WriteOptions? options}) async {
Future<ExecuteResult> registerName(String name,
{WriteOptions? options}) async {
final normalized = normalizeName(name);
final fee = options?.fee ?? await _nameFee();
return _exec(
Expand Down
3 changes: 2 additions & 1 deletion packages/dart/lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ class Coin {
final String denom;
final String amount;

Map<String, dynamic> toJson() => <String, dynamic>{'denom': denom, 'amount': amount};
Map<String, dynamic> toJson() =>
<String, dynamic>{'denom': denom, 'amount': amount};

@override
bool operator ==(Object other) =>
Expand Down
13 changes: 8 additions & 5 deletions packages/dart/lib/src/validation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@ bool _isAscii(String s) {
/// `reservedName`.
String normalizeName(String input) {
final trimmed = input.trim();
final bare =
trimmed.startsWith(namePrefix) ? trimmed.substring(namePrefix.length) : trimmed;
final bare = trimmed.startsWith(namePrefix)
? trimmed.substring(namePrefix.length)
: trimmed;
if (bare.isEmpty) {
throw const SafHandleError(SafHandleErrorCode.invalidName, 'Name is empty.');
throw const SafHandleError(
SafHandleErrorCode.invalidName, 'Name is empty.');
}

// Any `@` beyond the leading display marker means an email-shaped input.
Expand All @@ -68,8 +70,9 @@ String normalizeName(String input) {
}

final lowered = bare.toLowerCase();
final label =
lowered.endsWith(nameSuffix) ? lowered.substring(0, lowered.length - nameSuffix.length) : lowered;
final label = lowered.endsWith(nameSuffix)
? lowered.substring(0, lowered.length - nameSuffix.length)
: lowered;

// After stripping the single allowed `.saf` suffix, no dot may remain
// (blocks `john.com`, `a.b.saf`, and other domain/email-shaped input).
Expand Down
8 changes: 4 additions & 4 deletions packages/dart/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: safhandle
description: >-
Dart/Flutter SDK for the SafHandle name service on Safrochain — resolve wallet
addresses by name and register handles on-chain, with client-side validation
that mirrors the contract.
version: 0.2.1-beta
Dart/Flutter SDK for the SafHandle name service on Safrochain — resolve
wallet addresses by name and register handles on-chain, mirroring the
contract's validation.
version: 0.1.1-beta
repository: https://github.com/Safrochain-Org/safhandle-sdk
homepage: https://github.com/Safrochain-Org/safhandle-sdk/tree/main/packages/dart
issue_tracker: https://github.com/Safrochain-Org/safhandle-sdk/issues
Expand Down
10 changes: 7 additions & 3 deletions packages/dart/test/client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ SafHandleClient clientWith(MockClient mock) => SafHandleClient(
);

void main() {
test('getAddress builds a get_address smart query and parses the response', () async {
test('getAddress builds a get_address smart query and parses the response',
() async {
late Map<String, dynamic> seenQuery;
final mock = MockClient((request) async {
seenQuery = decodeQuery(request);
expect(request.url.path, contains('/cosmwasm/wasm/v1/contract/$contract/smart/'));
expect(request.url.path,
contains('/cosmwasm/wasm/v1/contract/$contract/smart/'));
return http.Response(
jsonEncode(<String, dynamic>{
'data': <String, dynamic>{
Expand Down Expand Up @@ -82,7 +84,9 @@ void main() {
test('getHandles parses a null name (address owns nothing)', () async {
final mock = MockClient((request) async {
return http.Response(
jsonEncode(<String, dynamic>{'data': <String, dynamic>{'name': null}}),
jsonEncode(<String, dynamic>{
'data': <String, dynamic>{'name': null}
}),
200,
);
});
Expand Down
14 changes: 9 additions & 5 deletions packages/dart/test/signing_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ class RecordingSigner implements SafHandleSigner {
// Path: /cosmwasm/wasm/v1/contract/{addr}/smart/{b64}
final segments = request.url.pathSegments;
final encoded = segments.last;
final decoded = jsonDecode(utf8.decode(base64.decode(Uri.decodeComponent(encoded))))
as Map<String, dynamic>;
final decoded =
jsonDecode(utf8.decode(base64.decode(Uri.decodeComponent(encoded))))
as Map<String, dynamic>;
if (decoded.containsKey('config')) {
configQueries++;
return http.Response(jsonEncode(<String, dynamic>{'data': config}), 200,
Expand All @@ -68,7 +69,8 @@ class RecordingSigner implements SafHandleSigner {
return (client: client, configQueries: () => configQueries);
}

SafHandleSigningClient makeClient(RecordingSigner signer, http.Client httpClient) {
SafHandleSigningClient makeClient(
RecordingSigner signer, http.Client httpClient) {
return SafHandleSigningClient(
signer: signer,
restEndpoint: 'https://rest.example',
Expand All @@ -79,7 +81,8 @@ SafHandleSigningClient makeClient(RecordingSigner signer, http.Client httpClient

void main() {
group('registerName', () {
test('normalizes the name and attaches the exact fee from config', () async {
test('normalizes the name and attaches the exact fee from config',
() async {
final signer = RecordingSigner();
final rest = fakeRest();
final client = makeClient(signer, rest.client);
Expand Down Expand Up @@ -128,7 +131,8 @@ void main() {
});

group('transfer / release (no funds)', () {
test('transferName sends normalized name and new owner with no funds', () async {
test('transferName sends normalized name and new owner with no funds',
() async {
final signer = RecordingSigner();
final client = makeClient(signer, fakeRest().client);

Expand Down
13 changes: 8 additions & 5 deletions packages/dart/test/validation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import 'package:test/test.dart';
// contract's validation.rs tests. If these drift from the contract,
// registrations built by the SDK would be rejected on-chain — so parity matters.

const addr = 'addr_safro10fmuatrxlcj6644vang5fuwyvdldfjss4tqqvemw9upm0qpn54esxr94v2';
const addr =
'addr_safro10fmuatrxlcj6644vang5fuwyvdldfjss4tqqvemw9upm0qpn54esxr94v2';

void main() {
group('normalizeName', () {
Expand Down Expand Up @@ -109,16 +110,18 @@ void main() {

group('parseInput', () {
test("routes '@name' to the name lane, normalized", () {
expect(parseInput('@john'), const ParsedInput(HandleKind.name, 'john.saf'));
expect(parseInput(' @Alice '), const ParsedInput(HandleKind.name, 'alice.saf'));
expect(
parseInput('@john'), const ParsedInput(HandleKind.name, 'john.saf'));
expect(parseInput(' @Alice '),
const ParsedInput(HandleKind.name, 'alice.saf'));
});

test('rejects an all-digit input as invalidInput', () {
for (final input in <String>['243899123456', '123']) {
expect(
() => parseInput(input),
throwsA(isA<SafHandleError>().having(
(e) => e.code, 'code', SafHandleErrorCode.invalidInput)),
throwsA(isA<SafHandleError>()
.having((e) => e.code, 'code', SafHandleErrorCode.invalidInput)),
reason: 'input: $input',
);
}
Expand Down
Loading