---
# System prepended metadata

title: Untitled

---

```
PIP: XX
Title: PascalCoin Multi-Approval Escrow
Type: Protocol
Impact: Hard-Fork
Author: Benjamin Ansbach
Status: Proposal
Created: 2019-11-21
```

## Abstract

This PIP proposes a simple, less-intrusive (development wise), multi-approval escrow sub-protocol for up to 6 participants. It will use the existing safebox capabilities and can be used with all available key types. 

## Copyright

WTFPL

## Presumptions

- The safebox information is always present on a node.
- We cannot use the account name to record data as it has to be globally unique. Therefore 32 bytes `SafeBox.Account.data` field will be used.
- A reservation for an `SafeBox.Account.type` value is made (500).
- Reservations for `BlockChain.Operation[DATA].data_type` is made (500, 501 and 502).

## Short description

By saving an escrow definition in the `SafeBox.Account.data` field and setting the `SafeBox.Account.type` to `500` the account will be in a new state called `escrow`. An Account in this state will have limited capabilities which are enforced by the node consensus implementation for this type of account.

It is less-intrusive because it's implementation is much simpler than adding MultiSig to the core and it works with any already existing Pascal key type. 

There are more solutions to this problem, especially by extending the SafeBox with more fields, but the author did not go that way on purpose.

The proposed solution is also completely trust-less, the third party can only act on behalf of the participants decisions: This is either..

- ..to send the escrow-locked amount to the beneficiary if enough participants agree or
- ..to send the escrow-locked amount back to it's original account.

## Setting up a multi-approval escrow PASA

The following will describe the workflow to set up an escrow account.

1. **Obtain an account**
   Obtain an account and name it as you wish, e.g. `ESCROW-ORDER-123` and make sure it's empty:

   1. `SafeBox.account.data = ''`
   2. `SafeBox.account.balance = 0`
   3. `SafeBox.account.type = 0`

   After the escrow-process is finished, the same account can be used for another escrow-process.
2. **Initiate a multi-approval escrow**
   Set the escrow definition and save it into `SafeBox.account.data` of the account:

   1. `min_approvals`
      The minimum number of approvals required for the account owner to dissolve the escrow.
   2. `origin`
      The account number where the balance originally came from and the amount can be send back to.
   3. `beneficiary`
      The account number of the potential beneficiary where the balance will be send to when there are enough approvals available.
   4. `participants`
      A list of maximum 5 account numbers that can approve or disapprove the payment.
   5. `flags`
      A list of 8 possible flags
      1. `owner_can_approve`
         A flag indicating whether the owner of the escrow is able to take part in the approval process, increasing the number of possible participants to 6.
      2. .. TBD
   6. `count_approvals (PROP2 ONLY)`
      The number of recorded approvals. On setup, this value must be set to zero. 


3. **Change account type to 500**
   Set the `SafeBox.Account.type` value to 500 (escrow).
4. **Send the escrow amount**
   Send the amount to be locked to the escrow account from the `SafeBox.Account.data.origin` account.

### Escrow state

As soon as the last step of the workflow above is completed, it will be considered a locked escrow account. An active escrow account is defined with the following conditions:

 - `SafeBox.Account.type = 500` 
 - a valid `SafeBox.Account.data` entry according to the escrow protocol specification.
 - `SafeBox.Account.balance > 0` 

Locked means, the account owner agreed to the protocol definition and also agreed to restrict the account to a limited set of operations:


- A Transaction of `SafeBox.Account.balance` back to `SafeBox.Account.data.origin`
- A Transaction of `SafeBox.Account.balance` to `SafeBox.Account.data.beneficiary` (in case there are enough approvals, explained below)
- A DATA operation to approve or disapprove a payment when the account is part of the participants (`SafeBox.Account.data.owner_can_auth`)

## Approve/Disapprove a payment

Any of the 5 mentioned accounts in `SafeBox.Account.data.participants` (plus the owner of the escrow-account if `SafeBox.Account.data.flags.owner_can_auth` is set) can issue an approval and initiate the dissolvement of the escrow by sending a DATA operation to the escrow account. 

The DATA operation to approve an escrow will have the `data_type` set to `500`. The payload contains the following values:

- `approval`
  A bool value indicating whether the account gives the approval or not. 
- `subject`
  Additional arbitrary information (e.g. reason for rejection, reason for approval).

When enough accounts send an approval operation (`SafeBox.Account.data.min_approvals`), the owner of the escrow account can issue a transaction of the full amount to `SafeBox.account.data.beneficiary`. 

The owner will **not** be able to send the amount back to the `SafeBox.account.data.origin` once there are enough approvals.

A technical definition of this payload "protocol" can be found at the end of the document.

## Finish an escrow

This approach reminds one of a smart contract functionality. 

The idea is that whenever an approval is sent to the escrow account, the value of  `SafeBox.Account.data.count_approvals` is incremented by one and the sender is removed from `SafeBox.Account.data.participants`. Therefore that the node will mutate the state of an account with each incoming approval operation. 

When the escrow owner sees that there are enough approvals available (via `SafeBox.Account.data.count_approvals`), he can issue a full payment to the beneficiary by sending a DATA operation with `data_type` = 502 with a payload of his choice.

The node will check if 

- `BlockChain.Operation.optype = 501`
- `BlockChain.Operation.amount + BlockChain.Operation.fee = SafeBox.Account.balance`

- the sender accounts `SafeBox.Account.data.count_approvals` is greater or equal the minimum required approvals.

When there are enough valid approvals per escrow definition and the operation is valid, the node will include the operation. If not it will be rejected. 

One could also think about automatically releasing the balance to the beneficiary, but this can be up to discussion.

A more technical example implementation is available at the end of the document.

## Cancel an escrow

An escrow can be canceled as long as there are not enough approvals available. It is canceled by sending the amount back to `SafeBox.Account.data.count_approvals`

## Protocol specification

### Escrow

To setup an escrow, the `SafeBox.Account.data` field will be initiated with the following data which ends up with 32 Bytes at most, which is the size of the field.

| Field                                                        | Type                              | Size       |
| ------------------------------------------------------------ | --------------------------------- | ---------- |
| `min_approvals`<br />The minimum number of approvals required for the owner to send the balance to the beneficiary. | `uint8`                           | 1 byte     |
| `origin`<br />The account that originally sent the amount.   | `uint32 LE`                       | 4 bytes    |
| `beneficiary`<br />The beneficiary account that will get the balance once there are enough approvals. | `uint32 LE`                       | 4 bytes    |
| `participant_count`<br />The number of participant accounts. | `uint8`                           | 1 byte     |
| `participants`<br />The list of participant account numbers. | `Array<participant_count>`        | 0-20 bytes |
| `participants[0..participant_count-1].account`<br />A single participant. | `uint32 LE`                       | -> 4 bytes |
| `flags`<br />A list of (max 8) flags.                        | `uint8`                           | 1 byte     |
| `flags.owner_can_approve`<br />Allow the owner of the escrow pasa to approve. | `flag 1`                          | 1 bit      |
| `flags.x`<br />Not defined yet.                              | `AnyOf<2, 4, 8, 16, 32, 64, 128>` |            |
| `count_approvals` <br />Initially always 0.                  | `int8`                            | 1 byte     |

### Approve

To approve an escrow the following payload together with a DATA operation with `data_type = 500` should be sent to the escrow account.

| Field                                |          |                |
| ------------------------------------ | -------- | -------------- |
| `approve`<br />0 = false, > 0 true   | `uint8`  | 1 Byte         |
| `subject`<br />Arbitrary information | `string` | [0..254] bytes |

### Finish

To successfully finish an escrow, the PASA owner can send the complete balance to the destination account using a DATA operation with the `data_type = 501`. The payload is irrelevant and can contain any arbitrary data.

### Cancel

To cancel an escrow, the PASA owner can send the complete balance to the original account using a DATA operation with the `data_type = 502`. The payload is irrelevant and can contain any arbitrary data.

## Consensus Rules to finish an operation

```bash
# .. Other standard rules apply
# ..

# Further rules to be discussed and validated.

# if the account is an escrow account
IF operation.optype = 10 AND # DATA
   operation.data_type IN [501, 502]
   operation.sender.account.type = 500 # escrow
THEN

  LET escrowAccount = operation.sender

  # if there is no balance in the account allow everything
  IF escrowAccount.balance = 0
  THEN
    EXECUTE(operation)
  ENDIF

  # parse account data
  LET escrowData = PARSE_ESCROW(escrowAccount.data)
  
  # if the data is invalid it's not a valid escrow
  IF INVALID(escrowData)
  THEN
    EXECUTE(operation)
  ENDIF

  # ------------------------------
  # we have a valid escrow account
  # ------------------------------

  # allow sending full amount back to origin
  IF operation.opType = 10 AND
     operation.dataType = 502 AND
     operation.target = escrowData.origin AND
     escrowAccount.balance = operation.amount + operation.fee AND
     escrowAccount.count_approvals < escrowData.min_approvals
  THEN
     EXECUTE(operation)
  ENDIF

  # validate sending the balance to the beneficiary
  IF operation.opType = 10 AND
     operation.dataType = 501 AND
     operation.target = escrowData.beneficiary AND
     operation.sender.balance = operation.amount + operation.fee AND
     escrowAccount.count_approvals >= escrowData.min_approvals
  THEN
     EXECUTE(operation);
  ENDIF
  
ENDIF
```

## Example

### Escrow data

| Field              | Value  | HexaString |
| ------------------ | ------ | ---------- |
| min_approvals      | 3      | `03`       |
| origin             | 1000   | `E8030000` |
| beneficiary        | 666    | `9A020000` |
| count_participants | 5      | `05`       |
| -> participant 1   | 1458   | `B2050000` |
| -> participant 2   | 17868  | `CC450000` |
| -> participant 3   | 145999 | `4F3A0200` |
| -> participant 4   | 254789 | `45E30300` |
| -> participant 5   | 98325  | `15800100` |
| flags              | 0      | `00`       |
| count_approvals    | 0      | `00`       |

```
03E80300009A02000005B2050000CC4500004F3A020045E30300158001000000
```

**SBX Code:**

```javascript
const Coding = require('@pascalcoin-sbx/common').Coding;
const Endian = require('@pascalcoin-sbx/common').Endian;
const AccountNumber = require('@pascalcoin-sbx/common').Types.AccountNumber;

const CompositeType = Coding.CompositeType;

class EscrowData extends CompositeType {
  constructor() {
    super('escrow');
    this.addSubType(new Coding.Core.Int8('minApprovals', true));
    this.addSubType(new Coding.Pascal.AccountNumber('origin'));
    this.addSubType(new Coding.Pascal.AccountNumber('beneficiary'));
    this.addSubType(new Coding.Core.Int8('participantCount', true));
    this.addSubType(
      new Coding.Repeating('participants',
        new Coding.Pascal.AccountNumber('participant'), 5, 'participantCount'
      )
    );
    this.addSubType(new Coding.Core.Int8('flags', true));
    this.addSubType(new Coding.Core.Int8('count_proposals', false)
      .withFixedValue(0));
  }
}

let escrow = {
  minApprovals: 3,
  origin: new AccountNumber(1000),
  beneficiary: new AccountNumber(666),
  participantCount: 5,
  participants: [
    new AccountNumber(1458),
    new AccountNumber(17868),
    new AccountNumber(145999),
    new AccountNumber(254789),
    new AccountNumber(98325)
  ],
  flags: 0
};

console.log(new EscrowData().encodeToBytes(escrow).toHex());
// 03E80300009A02000005B2050000CC4500004F3A020045E30300158001000000
```

### Approval payload

| Field   | Value                                  | HexaString                                                   |
| ------- | -------------------------------------- | ------------------------------------------------------------ |
| approve | 1                                      | 01                                                           |
| subject | I hereby approve paying out the escrow | `49206865726562792061`<br />`7070726F766520706179`<br />`696E67206F7574207468`<br />`6520657363726F77` |

```
01492068657265627920617070726F766520706179696E67206F75742074686520657363726F77
```

**SBX-Code**

```javascript
class ApprovalPayload extends CompositeType {
  constructor() {
    super('escrow_approve');
    this.addSubType(new Coding.Core.Int8('approve', true));
    this.addSubType(new Coding.Core.StringWithoutLength('subject'));
  }
}

let approval = {
  approve: 1,
  subject: "I hereby approve paying out the escrow"
};

console.log(new ApprovalPayload().encodeToBytes(approval).toHex());
// 01492068657265627920617070726F766520706179696E67206F75742074686520657363726F77

```

### Finish Payload `PROP1`

| Field              | Value         | HexaString                   |
| ------------------ | ------------- | ---------------------------- |
| approval_count     | 3             | `03`                         |
| Approval 1 Block   | 150001        | `F1490200`                   |
| Approval 1 OpBlock | 5             | `05000000`                   |
| Approval 2 Block   | 150007        | `F7490200`                   |
| Approval 2 OpBlock | 1             | `01000000`                   |
| Approval 3 Block   | 150012        | `FC490200`                   |
| Approval 3 OpBlock | 2             | `02000000`                   |
| Subject            | Approved by 3 | `417070726F7665642062792033` |

```
03F149020005000000F749020001000000FC49020002000000417070726F7665642062792033
```

```javascript
const Coding = require('@pascalcoin-sbx/common').Coding;
const Endian = require('@pascalcoin-sbx/common').Endian;
const AccountNumber = require('@pascalcoin-sbx/common').Types.AccountNumber;

const CompositeType = Coding.CompositeType;

class BlockAndOp extends CompositeType {
  constructor() {
    super('block_and_op');
    this.addSubType(new Coding.Core.Int32('block', true, Endian.LITTLE_ENDIAN));
    this.addSubType(new Coding.Core.Int32('opblock', true, Endian.LITTLE_ENDIAN));
  }
}

class FinishPayload extends CompositeType {
  constructor() {
    super('escrow_finish');
    this.addSubType(new Coding.Core.Int8('approvalCount', true));
    this.addSubType(
      new Coding.Repeating('approvals', new BlockAndOp('approval'), 6, 'approvalCount')
    );

    this.addSubType(new Coding.Core.StringWithoutLength('subject'));
  }
}

let finish = {
  approvalCount: 3,
  approvals: [
    { block: 150001, opblock: 5 },
    { block: 150007, opblock: 1 },
    { block: 150012, opblock: 2 },
  ],
  subject: "Approved by 3"
};

console.log(new FinishPayload().encodeToBytes(finish).toHex());
// 03F149020005000000F749020001000000FC49020002000000417070726F7665642062792033

```
