# app.gainforest.wallet.pendingSend

> Published by [gainforest.earth](https://lexicon.garden/identity/did:plc:qoti4acfmc5wg6zzmtix6hse)

✓ This is the authoritative definition for this NSID.

## Links

- [View on Lexicon Garden](https://lexicon.garden/lexicon/did:plc:qoti4acfmc5wg6zzmtix6hse/app.gainforest.wallet.pendingSend)
- [Documentation](https://lexicon.garden/lexicon/did:plc:qoti4acfmc5wg6zzmtix6hse/app.gainforest.wallet.pendingSend/docs)
- [Examples](https://lexicon.garden/lexicon/did:plc:qoti4acfmc5wg6zzmtix6hse/app.gainforest.wallet.pendingSend/examples)

## Definitions

### `app.gainforest.wallet.pendingSend`

**Type**: `record`

A transfer from the account's donation wallet (app.gainforest.wallet.primary) that still needs more passkey approvals. Multi-approval wallets (threshold > 1) collect approvals asynchronously: the initiator stores the exact unsigned ERC-4337 UserOperation here together with their own approval, remaining passkey holders approve from their own devices, and the final approver signs the full userOp hash which submits the operation on-chain and deletes this record. Every stored approval is a WebAuthn assertion over the operation's LIGHT hash (the SmartVault scheme where all but the last signer sign a gas-bounded subset of the operation). Approvals are verified off-chain before being stored and re-verified before submission, so a hand-crafted record cannot forge or brick a transfer. Fixed rkey `self`: one pending transfer per wallet at a time.

**Key**: `literal:self`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `to` | `string` | Yes | Recipient address (0x…, 20 bytes). Redundant with callData, for display; the server re-derives it from the operation. |
| `hash` | `string` | Yes | Full EntryPoint v0.7 userOp hash — signed by the final approver. |
| `token` | `string` | Yes | What is being sent. |
| `userOp` | `ref` → `#userOp` | Yes |  |
| `approvals` | `array` | Yes | Light-hash approvals collected so far, in signing order (at most threshold − 1; the final approval is never stored). |
| `createdAt` | `string` (datetime) | Yes |  |
| `createdBy` | `string` (did) | Yes | DID of the signed-in account that started the transfer. |
| `lightHash` | `string` | Yes | SmartVault light userOp hash — signed by every earlier approver. |
| `threshold` | `integer` | Yes | Approval requirement of the wallet when this transfer was started; a mismatch with the current wallet invalidates the transfer. |
| `amountUnits` | `string` | Yes | Raw token units as a decimal string. Redundant with callData, for display. |

### `app.gainforest.wallet.pendingSend#userOp`

**Type**: `object`

The unsigned ERC-4337 v0.7 UserOperation, exactly as it will be submitted. Gas and fee fields are decimal strings.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `nonce` | `string` | Yes | EntryPoint nonce at preparation time; consumed nonces invalidate the transfer. |
| `sender` | `string` | Yes | The wallet's vault address. |
| `factory` | `string` | No | SmartVaultFactory address — present only while the vault is undeployed. |
| `callData` | `string` | Yes | vault.execute(...) calldata — exactly one ETH/USDC/USDT transfer. |
| `factoryData` | `string` | No | createAccount calldata deploying the vault on first send. |
| `callGasLimit` | `string` | Yes |  |
| `maxFeePerGas` | `string` | Yes |  |
| `preVerificationGas` | `string` | Yes |  |
| `maxPriorityFeePerGas` | `string` | Yes |  |
| `verificationGasLimit` | `string` | Yes |  |

### `app.gainforest.wallet.pendingSend#approval`

**Type**: `object`

A WebAuthn assertion over the light userOp hash by one enrolled passkey signer.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `r` | `string` | Yes | secp256r1 signature r, decimal string. |
| `s` | `string` | Yes | secp256r1 signature s (low-s), decimal string. |
| `addedAt` | `string` (datetime) | Yes |  |
| `addedBy` | `string` (did) | Yes | DID of the signed-in account that added this approval. |
| `typeIndex` | `integer` | Yes | Index of "type" in clientDataJSON. |
| `credentialId` | `string` | Yes | WebAuthn credential id of the enrolled signer that approved. |
| `challengeIndex` | `integer` | Yes | Index of "challenge" in clientDataJSON. |
| `clientDataJSON` | `string` | Yes | Client data JSON of the assertion. |
| `authenticatorData` | `string` | Yes | Authenticator data of the assertion, hex. |

## Raw Schema

```json
{
  "id": "app.gainforest.wallet.pendingSend",
  "defs": {
    "main": {
      "key": "literal:self",
      "type": "record",
      "record": {
        "type": "object",
        "required": [
          "token",
          "to",
          "amountUnits",
          "userOp",
          "hash",
          "lightHash",
          "threshold",
          "approvals",
          "createdBy",
          "createdAt"
        ],
        "properties": {
          "to": {
            "type": "string",
            "description": "Recipient address (0x…, 20 bytes). Redundant with callData, for display; the server re-derives it from the operation."
          },
          "hash": {
            "type": "string",
            "description": "Full EntryPoint v0.7 userOp hash — signed by the final approver."
          },
          "token": {
            "enum": [
              "ETH",
              "USDC",
              "USDT"
            ],
            "type": "string",
            "description": "What is being sent."
          },
          "userOp": {
            "ref": "#userOp",
            "type": "ref"
          },
          "approvals": {
            "type": "array",
            "items": {
              "ref": "#approval",
              "type": "ref"
            },
            "description": "Light-hash approvals collected so far, in signing order (at most threshold − 1; the final approval is never stored)."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          },
          "createdBy": {
            "type": "string",
            "format": "did",
            "description": "DID of the signed-in account that started the transfer."
          },
          "lightHash": {
            "type": "string",
            "description": "SmartVault light userOp hash — signed by every earlier approver."
          },
          "threshold": {
            "type": "integer",
            "maximum": 255,
            "minimum": 2,
            "description": "Approval requirement of the wallet when this transfer was started; a mismatch with the current wallet invalidates the transfer."
          },
          "amountUnits": {
            "type": "string",
            "description": "Raw token units as a decimal string. Redundant with callData, for display."
          }
        }
      },
      "description": "A transfer from the account's donation wallet (app.gainforest.wallet.primary) that still needs more passkey approvals. Multi-approval wallets (threshold > 1) collect approvals asynchronously: the initiator stores the exact unsigned ERC-4337 UserOperation here together with their own approval, remaining passkey holders approve from their own devices, and the final approver signs the full userOp hash which submits the operation on-chain and deletes this record. Every stored approval is a WebAuthn assertion over the operation's LIGHT hash (the SmartVault scheme where all but the last signer sign a gas-bounded subset of the operation). Approvals are verified off-chain before being stored and re-verified before submission, so a hand-crafted record cannot forge or brick a transfer. Fixed rkey `self`: one pending transfer per wallet at a time."
    },
    "userOp": {
      "type": "object",
      "required": [
        "sender",
        "nonce",
        "callData",
        "callGasLimit",
        "verificationGasLimit",
        "preVerificationGas",
        "maxFeePerGas",
        "maxPriorityFeePerGas"
      ],
      "properties": {
        "nonce": {
          "type": "string",
          "description": "EntryPoint nonce at preparation time; consumed nonces invalidate the transfer."
        },
        "sender": {
          "type": "string",
          "description": "The wallet's vault address."
        },
        "factory": {
          "type": "string",
          "description": "SmartVaultFactory address — present only while the vault is undeployed."
        },
        "callData": {
          "type": "string",
          "description": "vault.execute(...) calldata — exactly one ETH/USDC/USDT transfer."
        },
        "factoryData": {
          "type": "string",
          "description": "createAccount calldata deploying the vault on first send."
        },
        "callGasLimit": {
          "type": "string"
        },
        "maxFeePerGas": {
          "type": "string"
        },
        "preVerificationGas": {
          "type": "string"
        },
        "maxPriorityFeePerGas": {
          "type": "string"
        },
        "verificationGasLimit": {
          "type": "string"
        }
      },
      "description": "The unsigned ERC-4337 v0.7 UserOperation, exactly as it will be submitted. Gas and fee fields are decimal strings."
    },
    "approval": {
      "type": "object",
      "required": [
        "credentialId",
        "authenticatorData",
        "clientDataJSON",
        "challengeIndex",
        "typeIndex",
        "r",
        "s",
        "addedBy",
        "addedAt"
      ],
      "properties": {
        "r": {
          "type": "string",
          "description": "secp256r1 signature r, decimal string."
        },
        "s": {
          "type": "string",
          "description": "secp256r1 signature s (low-s), decimal string."
        },
        "addedAt": {
          "type": "string",
          "format": "datetime"
        },
        "addedBy": {
          "type": "string",
          "format": "did",
          "description": "DID of the signed-in account that added this approval."
        },
        "typeIndex": {
          "type": "integer",
          "description": "Index of \"type\" in clientDataJSON."
        },
        "credentialId": {
          "type": "string",
          "description": "WebAuthn credential id of the enrolled signer that approved."
        },
        "challengeIndex": {
          "type": "integer",
          "description": "Index of \"challenge\" in clientDataJSON."
        },
        "clientDataJSON": {
          "type": "string",
          "description": "Client data JSON of the assertion."
        },
        "authenticatorData": {
          "type": "string",
          "description": "Authenticator data of the assertion, hex."
        }
      },
      "description": "A WebAuthn assertion over the light userOp hash by one enrolled passkey signer."
    }
  },
  "$type": "com.atproto.lexicon.schema",
  "lexicon": 1
}
```
