@gnolang/gnonative 4.7.2 → 4.8.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.
Files changed (35) hide show
  1. package/android/libs/gnocore-sources.jar +0 -0
  2. package/android/libs/gnocore.aar +0 -0
  3. package/android/src/main/java/land/gno/gnonative/GnonativeModule.kt +3 -1
  4. package/android/src/main/java/land/gno/gnonative/NativeDBManager.kt +325 -0
  5. package/build/api/GnoNativeApi.d.ts +3 -2
  6. package/build/api/GnoNativeApi.d.ts.map +1 -1
  7. package/build/api/GnoNativeApi.js +12 -3
  8. package/build/api/GnoNativeApi.js.map +1 -1
  9. package/build/api/types.d.ts +3 -2
  10. package/build/api/types.d.ts.map +1 -1
  11. package/build/api/types.js.map +1 -1
  12. package/build/api/vendor/gnonativetypes_pb.d.ts +91 -13
  13. package/build/api/vendor/gnonativetypes_pb.d.ts.map +1 -1
  14. package/build/api/vendor/gnonativetypes_pb.js +28 -18
  15. package/build/api/vendor/gnonativetypes_pb.js.map +1 -1
  16. package/build/api/vendor/rpc_pb.d.ts +19 -2
  17. package/build/api/vendor/rpc_pb.d.ts.map +1 -1
  18. package/build/api/vendor/rpc_pb.js +1 -1
  19. package/build/api/vendor/rpc_pb.js.map +1 -1
  20. package/ios/Frameworks/GnoCore.xcframework/Info.plist +5 -5
  21. package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/GnoCore +0 -0
  22. package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Headers/GnoGnonative.objc.h +32 -0
  23. package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/A/GnoCore +0 -0
  24. package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/A/Headers/GnoGnonative.objc.h +32 -0
  25. package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/Current/GnoCore +0 -0
  26. package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/Current/Headers/GnoGnonative.objc.h +32 -0
  27. package/ios/Frameworks/GnoCore.xcframework/ios-arm64_x86_64-simulator/GnoCore.framework/GnoCore +0 -0
  28. package/ios/Frameworks/GnoCore.xcframework/ios-arm64_x86_64-simulator/GnoCore.framework/Headers/GnoGnonative.objc.h +32 -0
  29. package/ios/GnonativeModule.swift +1 -0
  30. package/ios/NativeDBManager.swift +317 -0
  31. package/package.json +1 -1
  32. package/src/api/GnoNativeApi.ts +19 -4
  33. package/src/api/types.ts +8 -2
  34. package/src/api/vendor/gnonativetypes_pb.ts +124 -33
  35. package/src/api/vendor/rpc_pb.ts +20 -3
@@ -0,0 +1,317 @@
1
+ //
2
+ // NativeDBManager.swift
3
+ // Pods
4
+ //
5
+ // Created by Rémi BARBERO on 25/09/2025.
6
+ //
7
+
8
+ import Foundation
9
+ import Security
10
+ import GnoCore
11
+
12
+ public class NativeDBManager: NSObject, GnoGnonativeNativeDBProtocol {
13
+ public static var shared: NativeDBManager = NativeDBManager()
14
+
15
+ // MARK: - Private Properties
16
+ private let service: String
17
+ private let accessGroup: String?
18
+
19
+ // MARK: - Initialization
20
+ init(service: String = Bundle.main.bundleIdentifier ?? "GnoNativeService", accessGroup: String? = nil) {
21
+ self.service = service
22
+ self.accessGroup = accessGroup
23
+ }
24
+
25
+ // MARK: - Public Interface Implementation
26
+
27
+ public func get(_ key: Data?) throws -> Data {
28
+ guard let key = key, !key.isEmpty else {
29
+ throw NativeDBError.invalidArgument(description: "Key must not be nil or empty.")
30
+ }
31
+ let account = keyToAccount(key)
32
+
33
+ var query: [String: Any] = [
34
+ kSecClass as String: kSecClassGenericPassword,
35
+ kSecAttrService as String: service,
36
+ kSecAttrAccount as String: account,
37
+ kSecReturnData as String: true,
38
+ kSecMatchLimit as String: kSecMatchLimitOne
39
+ ]
40
+
41
+ if let accessGroup = accessGroup {
42
+ query[kSecAttrAccessGroup as String] = accessGroup
43
+ }
44
+
45
+ var result: AnyObject?
46
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
47
+
48
+ switch status {
49
+ case errSecSuccess:
50
+ return (result as? Data) ?? Data()
51
+ case errSecItemNotFound:
52
+ // This is not an error; it's the expected result for a missing key.
53
+ return Data()
54
+ default:
55
+ throw NativeDBError.keychainError(status: status, message: "Failed to get item.")
56
+ }
57
+ }
58
+
59
+ public func delete(_ key: Data?) throws {
60
+ try self.deleteSync(key)
61
+ }
62
+
63
+ public func deleteSync(_ key: Data?) throws {
64
+ guard let key = key, !key.isEmpty else {
65
+ throw NativeDBError.invalidArgument(description: "Key must not be nil or empty.")
66
+ }
67
+
68
+ let account = keyToAccount(key)
69
+
70
+ var query: [String: Any] = [
71
+ kSecClass as String: kSecClassGenericPassword,
72
+ kSecAttrService as String: service,
73
+ kSecAttrAccount as String: account
74
+ ]
75
+
76
+ if let accessGroup = accessGroup {
77
+ query[kSecAttrAccessGroup as String] = accessGroup
78
+ }
79
+
80
+ let status = SecItemDelete(query as CFDictionary)
81
+
82
+ // Deleting a non-existent item is not considered an error.
83
+ if status != errSecSuccess && status != errSecItemNotFound {
84
+ throw NativeDBError.keychainError(status: status, message: "Failed to delete item.")
85
+ }
86
+ }
87
+
88
+ public func has(_ key: Data?, ret0_ returnPointer: UnsafeMutablePointer<ObjCBool>?) throws {
89
+ // 1. Ensure the return pointer provided by the caller is valid.
90
+ guard let returnPointer = returnPointer else {
91
+ throw NativeDBError.invalidArgument(description: "Return pointer must not be nil.")
92
+ }
93
+
94
+ // 2. Validate the input key.
95
+ guard let key = key, !key.isEmpty else {
96
+ throw NativeDBError.invalidArgument(description: "Key must not be nil or empty.")
97
+ }
98
+
99
+ let account = keyToAccount(key)
100
+
101
+ var query: [String: Any] = [
102
+ kSecClass as String: kSecClassGenericPassword,
103
+ kSecAttrService as String: service,
104
+ kSecAttrAccount as String: account
105
+ ]
106
+ if let accessGroup = accessGroup {
107
+ query[kSecAttrAccessGroup as String] = accessGroup
108
+ }
109
+
110
+ let status = SecItemCopyMatching(query as CFDictionary, nil)
111
+
112
+ // 3. Handle the result from the Keychain.
113
+ switch status {
114
+ case errSecSuccess:
115
+ // Key exists. Write `true` to the pointer's memory location.
116
+ returnPointer.pointee = ObjCBool(true)
117
+ case errSecItemNotFound:
118
+ // Key does not exist. Write `false` to the pointer's memory location.
119
+ returnPointer.pointee = ObjCBool(false)
120
+ default:
121
+ // A real keychain error occurred. Throw an error.
122
+ throw NativeDBError.keychainError(status: status, message: "Failed to check for item existence.")
123
+ }
124
+ }
125
+
126
+ public func set(_ key: Data?, value: Data?) throws {
127
+ try self.setSync(key, value: value)
128
+ }
129
+
130
+ public func setSync(_ key: Data?, value: Data?) throws {
131
+ guard let key = key, !key.isEmpty else {
132
+ throw NativeDBError.invalidArgument(description: "Key must not be nil or empty.")
133
+ }
134
+ guard let value = value else {
135
+ throw NativeDBError.invalidArgument(description: "Value must not be nil.")
136
+ }
137
+
138
+ let account = keyToAccount(key)
139
+
140
+ // First, try to update existing item
141
+ var query: [String: Any] = [
142
+ kSecClass as String: kSecClassGenericPassword,
143
+ kSecAttrService as String: service,
144
+ kSecAttrAccount as String: account
145
+ ]
146
+
147
+ if let accessGroup = accessGroup {
148
+ query[kSecAttrAccessGroup as String] = accessGroup
149
+ }
150
+
151
+ let attributes: [String: Any] = [
152
+ kSecValueData as String: value
153
+ ]
154
+
155
+ let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
156
+
157
+ switch updateStatus {
158
+ case errSecSuccess:
159
+ // Update was successful.
160
+ return
161
+ case errSecItemNotFound:
162
+ // Item doesn't exist, so add it.
163
+ var newItem = query
164
+ newItem[kSecValueData as String] = value
165
+ newItem[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
166
+
167
+ let addStatus = SecItemAdd(newItem as CFDictionary, nil)
168
+ if addStatus != errSecSuccess {
169
+ throw NativeDBError.keychainError(status: addStatus, message: "Failed to add new item.")
170
+ }
171
+ default:
172
+ // Another error occurred during update.
173
+ throw NativeDBError.keychainError(status: updateStatus, message: "Failed to update item.")
174
+ }
175
+ }
176
+
177
+ public func scanChunk(_ start: Data?, end: Data?, seekKey: Data?, limit: Int, reverse: Bool) throws -> Data {
178
+ // 1) fetch all items for this service
179
+ var query: [String: Any] = [
180
+ kSecClass as String: kSecClassGenericPassword,
181
+ kSecAttrService as String: service,
182
+ kSecMatchLimit as String: kSecMatchLimitAll,
183
+ kSecReturnAttributes as String: true,
184
+ kSecReturnData as String: true,
185
+ ]
186
+
187
+ if let accessGroup = accessGroup {
188
+ query[kSecAttrAccessGroup as String] = accessGroup
189
+ }
190
+ var result: CFTypeRef?
191
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
192
+
193
+ var pairs: [(key: Data, val: Data)] = []
194
+ if status == errSecSuccess, let items = result as? [[String: Any]] {
195
+ for item in items {
196
+ guard let account = item[kSecAttrAccount as String] as? String,
197
+ let keyBytes = accountToKey(account),
198
+ let val = item[kSecValueData as String] as? Data else { continue }
199
+ if inRange(keyBytes, start: start, end: end) {
200
+ pairs.append((keyBytes, val))
201
+ }
202
+ }
203
+ }
204
+
205
+ // 2) sort
206
+ if reverse {
207
+ pairs.sort { a, b in
208
+ // descending: a > b
209
+ return lt(b.key, a.key)
210
+ }
211
+ } else {
212
+ pairs.sort { a, b in
213
+ // ascending: a < b
214
+ return lt(a.key, b.key)
215
+ }
216
+ }
217
+
218
+ // 3) apply seekKey (exclusive)
219
+ if let sk = seekKey, !sk.isEmpty {
220
+ if reverse {
221
+ // keep items with key < seekKey
222
+ let idx = pairs.firstIndex(where: { lt($0.key, sk) }) ?? pairs.count
223
+ // pairs are descending, so drop while key >= seekKey
224
+ pairs = Array(pairs[idx...])
225
+ } else {
226
+ // keep items with key > seekKey
227
+ let idx = pairs.lastIndex(where: { lte($0.key, sk) }) ?? -1
228
+ let startIdx = idx + 1
229
+ pairs = (startIdx < pairs.count) ? Array(pairs[startIdx...]) : []
230
+ }
231
+ }
232
+
233
+ // 4) limit
234
+ let lim = max(0, Int(limit))
235
+ let chunk = (lim > 0 && lim < pairs.count) ? Array(pairs.prefix(lim)) : pairs
236
+ let hasMore = chunk.count < pairs.count
237
+ let nextSeek = chunk.last?.key ?? Data()
238
+
239
+ // 6) Frame the blob
240
+ var blob = Data(capacity: 1 + 4) // will grow as needed
241
+ var flags: UInt8 = 0
242
+ if hasMore { flags |= 0x01 }
243
+ blob.append(&flags, count: 1)
244
+
245
+ var countBE = UInt32(chunk.count).bigEndian
246
+ withUnsafeBytes(of: &countBE) { blob.append($0.bindMemory(to: UInt8.self)) }
247
+
248
+ for (k, v) in chunk {
249
+ var klen = UInt32(k.count).bigEndian
250
+ var vlen = UInt32(v.count).bigEndian
251
+ withUnsafeBytes(of: &klen) { blob.append($0.bindMemory(to: UInt8.self)) }
252
+ blob.append(k)
253
+ withUnsafeBytes(of: &vlen) { blob.append($0.bindMemory(to: UInt8.self)) }
254
+ blob.append(v)
255
+ }
256
+
257
+ var nlen = UInt32(nextSeek.count).bigEndian
258
+ withUnsafeBytes(of: &nlen) { blob.append($0.bindMemory(to: UInt8.self)) }
259
+ blob.append(nextSeek)
260
+
261
+ return blob
262
+ }
263
+
264
+ // MARK: - Utility Methods
265
+
266
+ private func keyToAccount(_ key: Data) -> String {
267
+ return String(data: key, encoding: .utf8) ?? key.base64EncodedString()
268
+ }
269
+
270
+ private func accountToKey(_ account: String) -> Data? {
271
+ // Try UTF-8 first
272
+ if let utf8Data = account.data(using: .utf8) {
273
+ // Check if this was originally a base64 string by trying to decode it
274
+ if let base64Data = Data(base64Encoded: account), base64Data != utf8Data {
275
+ // This account was base64 encoded, return the decoded data
276
+ return base64Data
277
+ } else {
278
+ // This was a UTF-8 string, return the UTF-8 data
279
+ return utf8Data
280
+ }
281
+ }
282
+
283
+ // Fallback: try base64 decoding
284
+ return Data(base64Encoded: account)
285
+ }
286
+
287
+ // --- byte-wise comparisons on decoded keys ---
288
+ @inline(__always)
289
+ private func lt(_ a: Data, _ b: Data) -> Bool {
290
+ a.lexicographicallyPrecedes(b)
291
+ }
292
+ @inline(__always)
293
+ private func gte(_ a: Data, _ b: Data) -> Bool { !lt(a, b) }
294
+ @inline(__always)
295
+ private func lte(_ a: Data, _ b: Data) -> Bool { !lt(b, a) }
296
+ @inline(__always)
297
+ private func inRange(_ k: Data, start: Data?, end: Data?) -> Bool {
298
+ if let s = start, lt(k, s) { return false } // k >= s
299
+ if let e = end, !lt(k, e) { return false } // k < e
300
+ return true
301
+ }
302
+ }
303
+
304
+ public enum NativeDBError: Error, LocalizedError {
305
+ case invalidArgument(description: String)
306
+ case keychainError(status: OSStatus, message: String)
307
+
308
+ public var errorDescription: String? {
309
+ switch self {
310
+ case .invalidArgument(let description):
311
+ return "Invalid Argument: \(description)"
312
+ case .keychainError(let status, let message):
313
+ let statusMessage = SecCopyErrorMessageString(status, nil) as String? ?? "Unknown error"
314
+ return "Keychain Error: \(message) (status: \(status), reason: \(statusMessage))"
315
+ }
316
+ }
317
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnolang/gnonative",
3
- "version": "4.7.2",
3
+ "version": "4.8.0",
4
4
  "description": "Develop for Gno using your app's native language ",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -21,6 +21,7 @@ import {
21
21
  SignTxResponse,
22
22
  RotatePasswordResponse,
23
23
  EstimateGasResponse,
24
+ EstimateTxFeesResponse,
24
25
  } from './vendor/gnonativetypes_pb';
25
26
  import { GnoNativeService } from './vendor/rpc_pb';
26
27
  import { GoBridge, GoBridgeInterface } from '../GoBridge';
@@ -109,8 +110,6 @@ export class GnoNativeApi implements GnoKeyApi, GoBridgeInterface {
109
110
  address: Uint8Array,
110
111
  securityMargin: number,
111
112
  updateTx: boolean,
112
- accountNumber: bigint = BigInt(0),
113
- sequenceNumber: bigint = BigInt(0),
114
113
  ): Promise<EstimateGasResponse> {
115
114
  const client = this.#getClient();
116
115
  const response = client.estimateGas({
@@ -118,8 +117,24 @@ export class GnoNativeApi implements GnoKeyApi, GoBridgeInterface {
118
117
  address,
119
118
  securityMargin,
120
119
  updateTx,
121
- accountNumber,
122
- sequenceNumber,
120
+ });
121
+ return response;
122
+ }
123
+
124
+ async estimateTxFees(
125
+ txJson: string,
126
+ address: Uint8Array,
127
+ gasSecurityMargin: number,
128
+ gasPriceSecurityMargin: number,
129
+ updateTx: boolean,
130
+ ): Promise<EstimateTxFeesResponse> {
131
+ const client = this.#getClient();
132
+ const response = client.estimateTxFees({
133
+ txJson,
134
+ address,
135
+ gasSecurityMargin,
136
+ gasPriceSecurityMargin,
137
+ updateTx,
123
138
  });
124
139
  return response;
125
140
  }
package/src/api/types.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  KeyInfo,
16
16
  SignTxResponse,
17
17
  EstimateGasResponse,
18
+ EstimateTxFeesResponse,
18
19
  MakeTxResponse,
19
20
  BroadcastTxCommitResponse,
20
21
  Coin,
@@ -116,9 +117,14 @@ export interface GnoKeyApi {
116
117
  address: Uint8Array,
117
118
  securityMargin: number,
118
119
  updateTx: boolean,
119
- accountNumber?: bigint,
120
- sequenceNumber?: bigint,
121
120
  ): Promise<EstimateGasResponse>;
121
+ estimateTxFees(
122
+ txJson: string,
123
+ address: Uint8Array,
124
+ gasSecurityMargin: number,
125
+ getPriceSecurityMargin: number,
126
+ updateTx: boolean,
127
+ ): Promise<EstimateTxFeesResponse>;
122
128
  makeCallTx(
123
129
  packagePath: string,
124
130
  fnc: string,