feat(block2): Ğ1 — publish a Ğ1 address + 'Pay in Ğ1' in chat (levels 1-2)
The optional Ğ1 (free currency) integration, per g1-integration.md. - Profile: an optional 'Ğ1 address' field, persisted locally and published in your NIP-01 kind:0 metadata (separate from the Nostr key). - commons_core: UserProfile/ProfileTransport carry a 'g1' field (round-trip tested). - Chat: when the peer published a Ğ1 address, a 'Pay in Ğ1' app-bar action opens their wallet (url_launcher) so value moves in the wallet, not the app — copies the address as a fallback. No payment rails in-app (sharing-model §4.3). NOTE (vjrj): the wallet deep-link uses the Cesium web wallet as a universal fallback; swap the URI in chat_screen._payG1 for the Ğ1nkgo/Ğecko scheme you prefer. Analyzer clean (both packages); profile transport test covers g1.
This commit is contained in:
parent
d0d2ecb132
commit
d7136ec2c2
13 changed files with 138 additions and 14 deletions
|
|
@ -19,11 +19,13 @@ class NostrProfileTransport implements ProfileTransport {
|
|||
String name = '',
|
||||
String about = '',
|
||||
String picture = '',
|
||||
String g1 = '',
|
||||
}) async {
|
||||
final content = jsonEncode({
|
||||
if (name.isNotEmpty) 'name': name,
|
||||
if (about.isNotEmpty) 'about': about,
|
||||
if (picture.isNotEmpty) 'picture': picture,
|
||||
if (g1.isNotEmpty) 'g1': g1,
|
||||
});
|
||||
final event = Event.from(
|
||||
kind: kindMetadata,
|
||||
|
|
@ -50,6 +52,7 @@ class NostrProfileTransport implements ProfileTransport {
|
|||
name: (map['name'] as String?) ?? '',
|
||||
about: (map['about'] as String?) ?? '',
|
||||
picture: (map['picture'] as String?) ?? '',
|
||||
g1: (map['g1'] as String?) ?? '',
|
||||
);
|
||||
} catch (_) {
|
||||
return UserProfile(pubkeyHex: pubkeyHex);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ class UserProfile {
|
|||
this.name = '',
|
||||
this.about = '',
|
||||
this.picture = '',
|
||||
this.g1 = '',
|
||||
});
|
||||
|
||||
final String pubkeyHex;
|
||||
|
|
@ -13,7 +14,12 @@ class UserProfile {
|
|||
final String about;
|
||||
final String picture;
|
||||
|
||||
bool get isEmpty => name.isEmpty && about.isEmpty && picture.isEmpty;
|
||||
/// Optional Ğ1 (Duniter) address the person chose to publish, so peers can pay
|
||||
/// them in Ğ1. Separate from the Nostr key; empty when not shared.
|
||||
final String g1;
|
||||
|
||||
bool get isEmpty =>
|
||||
name.isEmpty && about.isEmpty && picture.isEmpty && g1.isEmpty;
|
||||
}
|
||||
|
||||
/// Publish/fetch public profile metadata. Kind:0 is replaceable — publishing
|
||||
|
|
@ -21,7 +27,7 @@ class UserProfile {
|
|||
/// sibling of the offer/message/trust transports over the same connection.
|
||||
abstract interface class ProfileTransport {
|
||||
/// Publishes (replaces) this identity's profile.
|
||||
Future<void> publish({String name, String about, String picture});
|
||||
Future<void> publish({String name, String about, String picture, String g1});
|
||||
|
||||
/// Fetches [pubkeyHex]'s latest profile, or null if they never published one.
|
||||
Future<UserProfile?> fetch(String pubkeyHex);
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@ void main() {
|
|||
test('publish then fetch round-trips a profile (kind:0)', () async {
|
||||
final alice = await idFor(1);
|
||||
final aliceConn = await NostrConnection.connect(relay.url, identity: alice);
|
||||
await NostrProfileTransport(aliceConn)
|
||||
.publish(name: 'Alicia', about: 'Guardo tomate rosa');
|
||||
await NostrProfileTransport(aliceConn).publish(
|
||||
name: 'Alicia',
|
||||
about: 'Guardo tomate rosa',
|
||||
g1: 'G1abc123',
|
||||
);
|
||||
|
||||
// Anyone else can fetch it.
|
||||
final bob = await idFor(2);
|
||||
|
|
@ -28,6 +31,7 @@ void main() {
|
|||
expect(fetched, isNotNull);
|
||||
expect(fetched!.name, 'Alicia');
|
||||
expect(fetched.about, 'Guardo tomate rosa');
|
||||
expect(fetched.g1, 'G1abc123');
|
||||
|
||||
await aliceConn.close();
|
||||
await bobConn.close();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue