Skip to main content

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

The signDelegateActions() method returns a DelegateActionResult:
public struct DelegateActionResult {
    /// Raw JSON result from wallet containing signed delegate actions
    public let rawResult: String?
}
The 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
                ]
            ]
        ]
    ]
]
// 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
                ]
            ]
        ]
    ]
]

Next Steps

Build docs developers (and LLMs) love