@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.
- package/android/libs/gnocore-sources.jar +0 -0
- package/android/libs/gnocore.aar +0 -0
- package/android/src/main/java/land/gno/gnonative/GnonativeModule.kt +3 -1
- package/android/src/main/java/land/gno/gnonative/NativeDBManager.kt +325 -0
- package/build/api/GnoNativeApi.d.ts +3 -2
- package/build/api/GnoNativeApi.d.ts.map +1 -1
- package/build/api/GnoNativeApi.js +12 -3
- package/build/api/GnoNativeApi.js.map +1 -1
- package/build/api/types.d.ts +3 -2
- package/build/api/types.d.ts.map +1 -1
- package/build/api/types.js.map +1 -1
- package/build/api/vendor/gnonativetypes_pb.d.ts +91 -13
- package/build/api/vendor/gnonativetypes_pb.d.ts.map +1 -1
- package/build/api/vendor/gnonativetypes_pb.js +28 -18
- package/build/api/vendor/gnonativetypes_pb.js.map +1 -1
- package/build/api/vendor/rpc_pb.d.ts +19 -2
- package/build/api/vendor/rpc_pb.d.ts.map +1 -1
- package/build/api/vendor/rpc_pb.js +1 -1
- package/build/api/vendor/rpc_pb.js.map +1 -1
- package/ios/Frameworks/GnoCore.xcframework/Info.plist +5 -5
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/GnoCore +0 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Headers/GnoGnonative.objc.h +32 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/A/GnoCore +0 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/A/Headers/GnoGnonative.objc.h +32 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/Current/GnoCore +0 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/Current/Headers/GnoGnonative.objc.h +32 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64_x86_64-simulator/GnoCore.framework/GnoCore +0 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64_x86_64-simulator/GnoCore.framework/Headers/GnoGnonative.objc.h +32 -0
- package/ios/GnonativeModule.swift +1 -0
- package/ios/NativeDBManager.swift +317 -0
- package/package.json +1 -1
- package/src/api/GnoNativeApi.ts +19 -4
- package/src/api/types.ts +8 -2
- package/src/api/vendor/gnonativetypes_pb.ts +124 -33
- 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
package/src/api/GnoNativeApi.ts
CHANGED
|
@@ -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
|
-
|
|
122
|
-
|
|
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,
|