Documentation Index
Fetch the complete documentation index at: https://mintlify.com/frol/near-connect-ios/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Delegate actions (NEP-366) enable meta transactions where a relayer pays for gas on behalf of users. The wallet signs actions without broadcasting them, and a relayer service submits the signed actions to the network. This enables:- Gasless transactions for users
- Sponsored experiences where apps pay gas fees
- Batch operations signed once, executed by a relayer
Sign Delegate Actions
Sign actions without broadcasting:let delegateActions: [[String: Any]] = [
[
"receiverId": "guest-book.near",
"actions": [
[
"type": "FunctionCall",
"params": [
"methodName": "add_message",
"args": argsBase64, // base64-encoded JSON
"gas": "30000000000000",
"deposit": "0"
]
]
]
]
]
do {
let result = try await walletManager.signDelegateActions(
delegateActions: delegateActions
)
// Send signed actions to relayer
if let signedPayload = result.rawResult {
try await submitToRelayer(signedPayload)
}
} catch {
print("Signing failed: \(error.localizedDescription)")
}
Method Parameters
public func signDelegateActions(
delegateActions: [[String: Any]] // Array of delegate action dictionaries
) async throws -> DelegateActionResult
Delegate Action Structure
Each delegate action contains:- receiverId: The contract to call
- actions: Array of actions to execute (FunctionCall, Transfer, etc.)
let delegateAction: [String: Any] = [
"receiverId": "contract.near",
"actions": [
[
"type": "FunctionCall",
"params": [
"methodName": "method_name",
"args": "base64-encoded-args",
"gas": "30000000000000",
"deposit": "0"
]
]
]
]
Function arguments must be base64-encoded JSON, not plain dictionaries like in
callFunction().Complete Example
Here’s the real delegate actions implementation from the example app:import SwiftUI
import NEARConnect
struct DelegateActionDemoView: View {
@EnvironmentObject var walletManager: NEARWalletManager
@State private var receiverId = "guest-book.near"
@State private var methodName = "add_message"
@State private var argsText = "{\"text\": \"Hello via meta tx!\"}"
@State private var isProcessing = false
@State private var showError = false
@State private var errorMessage = ""
var body: some View {
Form {
Section {
Text("Sign delegate actions for meta transactions (NEP-366). The wallet signs but does not broadcast — a relayer submits them.")
.font(.subheadline)
.foregroundColor(.secondary)
}
Section(header: Text("Delegate Action")) {
HStack {
Text("Signer")
.foregroundColor(.secondary)
Spacer()
Text(walletManager.currentAccount?.accountId ?? "")
.font(.caption)
}
TextField("Receiver ID", text: $receiverId)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
TextField("Method Name", text: $methodName)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
VStack(alignment: .leading, spacing: 4) {
Text("Arguments (JSON)")
.font(.caption)
.foregroundColor(.secondary)
TextEditor(text: $argsText)
.font(.system(.body, design: .monospaced))
.frame(height: 80)
}
}
Section {
Button(action: signDelegate) {
HStack {
Spacer()
if isProcessing {
ProgressView()
.padding(.trailing, 8)
}
Text(isProcessing ? "Signing..." : "Sign Delegate Actions")
Spacer()
}
}
.disabled(isProcessing || receiverId.isEmpty || methodName.isEmpty)
}
Section {
Text("The signed delegate actions can be submitted to the network by a relayer service that pays for gas.")
.font(.caption)
.foregroundColor(.secondary)
}
}
.alert("Error", isPresented: $showError) {
Button("OK", role: .cancel) { }
} message: {
Text(errorMessage)
}
}
private func signDelegate() {
guard let argsData = argsText.data(using: .utf8),
let args = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] else {
errorMessage = "Invalid JSON arguments"
showError = true
return
}
let argsJSON = try? JSONSerialization.data(withJSONObject: args)
let argsBase64 = argsJSON?.base64EncodedString() ?? ""
let delegateActions: [[String: Any]] = [
[
"receiverId": receiverId,
"actions": [
[
"type": "FunctionCall",
"params": [
"methodName": methodName,
"args": argsBase64,
"gas": "30000000000000",
"deposit": "0"
]
]
]
]
]
Task {
isProcessing = true
defer { isProcessing = false }
do {
let delegateResult = try await walletManager.signDelegateActions(
delegateActions: delegateActions
)
let output = delegateResult.rawResult ?? "(no raw result returned)"
print("✅ Signed delegate actions: \(output)")
// In production: send to relayer
// try await submitToRelayer(output)
} catch {
errorMessage = error.localizedDescription
showError = true
}
}
}
}
Delegate Action Result
ThesignDelegateActions() method returns a DelegateActionResult:
public struct DelegateActionResult {
/// Raw JSON result from wallet containing signed delegate actions
public let rawResult: String?
}
rawResult contains the signed payload ready to submit to a relayer.
Submit to Relayer
After signing, send the signed actions to your relayer service:func submitToRelayer(_ signedPayload: String) async throws {
struct RelayerRequest: Codable {
let signedDelegateActions: String
}
let request = RelayerRequest(signedDelegateActions: signedPayload)
let jsonData = try JSONEncoder().encode(request)
var urlRequest = URLRequest(url: URL(string: "https://relayer.myapp.com/submit")!)
urlRequest.httpMethod = "POST"
urlRequest.httpBody = jsonData
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
let (responseData, _) = try await URLSession.shared.data(for: urlRequest)
struct RelayerResponse: Codable {
let transactionHash: String
}
let response = try JSONDecoder().decode(RelayerResponse.self, from: responseData)
print("Transaction submitted: \(response.transactionHash)")
}
// Usage
let result = try await walletManager.signDelegateActions(delegateActions: actions)
if let signedPayload = result.rawResult {
try await submitToRelayer(signedPayload)
}
Common Action Types
Function Call
let functionCallAction: [String: Any] = [
"type": "FunctionCall",
"params": [
"methodName": "add_message",
"args": argsBase64,
"gas": "30000000000000",
"deposit": "0"
]
]
Transfer NEAR
let transferAction: [String: Any] = [
"type": "Transfer",
"params": [
"deposit": "1000000000000000000000000" // 1 NEAR in yoctoNEAR
]
]
let delegateActions: [[String: Any]] = [
[
"receiverId": "bob.near",
"actions": [transferAction]
]
]
Create Account
let createAccountAction: [String: Any] = [
"type": "CreateAccount",
"params": [:]
]
let delegateActions: [[String: Any]] = [
[
"receiverId": "newaccount.near",
"actions": [createAccountAction]
]
]
Add Access Key
let addKeyAction: [String: Any] = [
"type": "AddKey",
"params": [
"publicKey": "ed25519:...",
"accessKey": [
"nonce": 0,
"permission": "FullAccess" // or FunctionCall permission
]
]
]
Multiple Actions in One Transaction
Combine multiple actions in a single delegate action:let argsBase64 = try JSONSerialization.data(
withJSONObject: ["text": "Hello!"]
).base64EncodedString()
let delegateActions: [[String: Any]] = [
[
"receiverId": "multi.near",
"actions": [
[
"type": "FunctionCall",
"params": [
"methodName": "action_one",
"args": argsBase64,
"gas": "30000000000000",
"deposit": "0"
]
],
[
"type": "FunctionCall",
"params": [
"methodName": "action_two",
"args": argsBase64,
"gas": "30000000000000",
"deposit": "0"
]
]
]
]
]
Multiple Delegate Actions
Sign actions for multiple receivers:let delegateActions: [[String: Any]] = [
[
"receiverId": "contract1.near",
"actions": [/* actions for contract1 */]
],
[
"receiverId": "contract2.near",
"actions": [/* actions for contract2 */]
]
]
let result = try await walletManager.signDelegateActions(
delegateActions: delegateActions
)
Error Handling
do {
let result = try await walletManager.signDelegateActions(
delegateActions: delegateActions
)
print("✅ Delegate actions signed")
} catch NEARError.notSignedIn {
print("❌ Please connect a wallet first")
} catch NEARError.operationInProgress {
print("❌ Another operation is in progress")
} catch NEARError.walletError(let message) {
if message.contains("not support") {
print("❌ Wallet does not support delegate actions")
} else if message.contains("rejected") {
print("User rejected signing")
} else {
print("❌ Wallet error: \(message)")
}
} catch {
print("❌ Unexpected error: \(error.localizedDescription)")
}
Not all wallets support NEP-366 delegate actions. Gracefully handle wallets that don’t support this feature.
Building a Relayer Service
A relayer service receives signed delegate actions and submits them to NEAR:const { connect, KeyPair } = require('near-api-js');
class NEARRelayer {
async submitDelegateAction(signedPayload) {
// Parse signed delegate action
const payload = JSON.parse(signedPayload);
// Connect to NEAR
const near = await connect({
networkId: 'mainnet',
nodeUrl: 'https://rpc.mainnet.near.org',
// Relayer's key pair (pays for gas)
keyStore: myKeyStore,
});
const account = await near.account('relayer.near');
// Submit the signed delegate action
const result = await account.signedDelegate(payload);
return {
transactionHash: result.transaction.hash,
status: result.status
};
}
}
Use Cases
Gasless Onboarding
// User creates account without owning NEAR
let createArgs = ["account_id": "newuser.near"]
let argsBase64 = try JSONSerialization.data(
withJSONObject: createArgs
).base64EncodedString()
let delegateActions: [[String: Any]] = [
[
"receiverId": "account-factory.near",
"actions": [
[
"type": "FunctionCall",
"params": [
"methodName": "create_account",
"args": argsBase64,
"gas": "50000000000000",
"deposit": "1000000000000000000000000" // Relayer pays
]
]
]
]
]
Sponsored Transactions
// App sponsors NFT minting for users
let mintArgs = ["token_id": "123", "metadata": metadata]
let argsBase64 = try JSONSerialization.data(
withJSONObject: mintArgs
).base64EncodedString()
let delegateActions: [[String: Any]] = [
[
"receiverId": "nft.myapp.near",
"actions": [
[
"type": "FunctionCall",
"params": [
"methodName": "nft_mint",
"args": argsBase64,
"gas": "100000000000000",
"deposit": "8000000000000000000000" // Storage fee paid by relayer
]
]
]
]
]