Skip to content

Commit

Permalink
simple ordinal send p2p protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonbryant12 authored and sirdeggen committed Apr 4, 2024
1 parent ff551f8 commit ad3adeb
Show file tree
Hide file tree
Showing 8 changed files with 471 additions and 0 deletions.
89 changes: 89 additions & 0 deletions docs/paymailClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,22 @@ export default class PaymailClient {
}
return response;
};
public getP2pOrdinalDestinations = async (paymail: string, ordinals: number): Promise<any> => {
const response = await this.request(paymail, SimpleP2pOrdinalDestinationsCapability, {
ordinals
});
const schema = Joi.object({
outputs: Joi.array().items(Joi.object({
script: Joi.string().required()
}).required().min(1)),
reference: Joi.string().required()
}).options({ stripUnknown: true });
const { error } = schema.validate(response);
if (error) {
throw new PaymailServerResponseError(`Validation error: ${error.message}`);
}
return response;
};
public sendTransactionP2P = async (paymail: string, hex: string, reference: string, metadata?: {
sender: string;
pubkey: string;
Expand All @@ -156,6 +172,27 @@ export default class PaymailClient {
}
return value;
};
public sendOrdinalTransactionP2P = async (paymail: string, hex: string, reference: string, metadata?: {
sender: string;
pubkey: string;
signature: string;
note: string;
}) => {
const response = await this.request(paymail, SimpleP2pOrdinalReceiveCapability, {
hex,
reference,
metadata
});
const schema = Joi.object({
txid: Joi.string().required(),
note: Joi.string().optional().allow("")
}).options({ stripUnknown: true });
const { error, value } = schema.validate(response);
if (error) {
throw new PaymailServerResponseError(`Validation error: ${error.message}`);
}
return value;
};
public createP2PSignature = (msg: string, privKey: PrivateKey): string => {
const msgHash = new BigNumber(sha256(msg, "hex"), 16);
const sig = ECDSA.sign(msgHash, privKey, true);
Expand Down Expand Up @@ -252,6 +289,29 @@ public ensureCapabilityFor = async (aDomain, aCapability) => {
}
```

### Property getP2pOrdinalDestinations

Requests a P2P ordinal destination for a given Paymail.

```ts
public getP2pOrdinalDestinations = async (paymail: string, ordinals: number): Promise<any> => {
const response = await this.request(paymail, SimpleP2pOrdinalDestinationsCapability, {
ordinals
});
const schema = Joi.object({
outputs: Joi.array().items(Joi.object({
script: Joi.string().required()
}).required().min(1)),
reference: Joi.string().required()
}).options({ stripUnknown: true });
const { error } = schema.validate(response);
if (error) {
throw new PaymailServerResponseError(`Validation error: ${error.message}`);
}
return response;
}
```

### Property getP2pPaymentDestination

Requests a P2P payment destination for a given Paymail.
Expand Down Expand Up @@ -364,6 +424,35 @@ public sendBeefTransactionP2P = async (paymail: string, beef: string, reference:
}
```

### Property sendOrdinalTransactionP2P

Sends a transaction using the Pay-to-Peer (P2P) protocol.
This method is used to send a transaction to a Paymail address.

```ts
public sendOrdinalTransactionP2P = async (paymail: string, hex: string, reference: string, metadata?: {
sender: string;
pubkey: string;
signature: string;
note: string;
}) => {
const response = await this.request(paymail, SimpleP2pOrdinalReceiveCapability, {
hex,
reference,
metadata
});
const schema = Joi.object({
txid: Joi.string().required(),
note: Joi.string().optional().allow("")
}).options({ stripUnknown: true });
const { error, value } = schema.validate(response);
if (error) {
throw new PaymailServerResponseError(`Validation error: ${error.message}`);
}
return value;
}
```

### Property sendTransactionP2P

Sends a transaction using the Pay-to-Peer (P2P) protocol.
Expand Down
103 changes: 103 additions & 0 deletions docs/specs/ordinals/simpleP2PSend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
### Simple P2P Ordinal Transactions Specification

---

#### 1. Simple P2P Ordinal Payment Destinations


This protocol provides a mechanism for requesting payment destinations specifically for ordinal transactions. It is similar to the P2P Payment Destination protocol but tailored for handling simple ordinal sends.

#### Motivation
Ordinals introduce a specific need in the Bitcoin ecosystem for transaction types that are simpler and more focused. While existing P2P payment protocols provide a broad range of functionalities, there is a demand for a protocol that specifically caters to ordinal transactions. This need arises from the unique characteristics of ordinals, ordinal transactions can not be differentiated from regular bitcoin transactions so it is needed for a specific path where only ordinal transactions go though to prevent accidental burns of an ordinal

#### Capability Discovery

The `.well-known/bsvalias` document is updated to include a declaration of the endpoint for ordinal payment destinations:

```json
{
"bsvalias": "1.0",
"capabilities": {
"cc2154bfa6a2": "https://example.bsvalias.tld/api/ordinal-p2p-payment-destination/{alias}@{domain.tld}"
}
}
```

The capability `cc2154bfa6a2` returns a URI template where the client must perform a POST request with the number of ordinals to be sent.


#### Client Request

The request body structure:

```json
{
"ordinals": 2
}
```

#### Server Response

```json
{
"outputs": [
{
"script": "hex-encoded-locking-script"
},
{
"script": "another-or-the-same-hex-locking-script"
}
],
"reference": "reference-id"
}
```

#### 2. Simple P2P Ordinal Receive Transaction
This protocol allows Paymail providers to receive simple ordinal transactions sent to their users.

#### Motivation

The growing usage of ordinals requires a dedicated protocol for receiving such transactions. The existing P2P transaction protocols handle a wide array of transaction types, but a more focused approach is needed for the simplicity and specificity of ordinal transactions.

#### Capability Discovery

The `.well-known/bsvalias` document includes a declaration for the ordinal transaction receiving endpoint:

```json
{
"bsvalias": "1.0",
"capabilities": {
"2cc00c7f93c3": "https://example.bsvalias.tld/api/receive-ordinal-tx/{alias}@{domain.tld}"
}
}
```

#### Client Request

The request body structure:

```json
{
"hex": "hex-encoded-transaction",
"reference": "reference-id",
"metadata": {
"sender": "someone@example.tld",
"pubkey": "public-key",
"signature": "signature",
"note": "transaction-note"
}
}
```


#### Server Response
```json
{
"txid": "transaction-id",
"note": "optional-note"
}
```

### Conclusion

These simple ordinal transaction capabilities are designed for straightforward and efficient handling of ordinal transactions. They complement the existing Paymail capabilities by focusing on a specific transaction type. As the ecosystem evolves, further protocols may be developed to accommodate more complex functions involving ordinals.
9 changes: 9 additions & 0 deletions src/capability/simpleP2pOrdinalDestinationsCapability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Capability from './capability.js'

export default new Capability({
title: 'Simple P2P Ordinal Destinations',
authors: ['Brandon Bryant (HandCash)'],
version: '1',
method: 'POST',
code: 'cc2154bfa6a2'
})
9 changes: 9 additions & 0 deletions src/capability/simpleP2pOrdinalReceiveCapability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Capability from './capability.js'

export default new Capability({
title: 'Simple P2P Ordinal Receive Transaction',
authors: ['Brandon Bryant (HandCash)'],
version: '1',
method: 'POST',
code: '2cc00c7f93c3'
})
61 changes: 61 additions & 0 deletions src/paymailClient/paymailClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import P2pPaymentDestinationCapability from '../capability/p2pPaymentDestination
import ReceiveTransactionCapability from '../capability/p2pReceiveTransactionCapability.js'
import VerifyPublicKeyOwnerCapability from '../capability/verifyPublicKeyOwnerCapability.js'
import ReceiveBeefTransactionCapability from '../capability/p2pReceiveBeefTransactionCapability.js'
import SimpleP2pOrdinalDestinationsCapability from '../capability/simpleP2pOrdinalDestinationsCapability.js'
import SimpleP2pOrdinalReceiveCapability from '../capability/simpleP2pOrdinalReceiveCapability.js'
const { sha256 } = Hash

/**
Expand Down Expand Up @@ -188,6 +190,31 @@ export default class PaymailClient {
return response
}

/**
* Requests a P2P ordinal destination for a given Paymail.
* @param paymail - The Paymail address to request the payment destination for.
* @param ordinals - The amount of ordinals to be sent in transaction
* @returns An object containing the ordinal destination details.
*/
public getP2pOrdinalDestinations = async (paymail: string, ordinals: number): Promise<any> => {
const response = await this.request(paymail, SimpleP2pOrdinalDestinationsCapability, {
ordinals
})

const schema = Joi.object({
outputs: Joi.array().items(
Joi.object({
script: Joi.string().required()
}).required().min(1)),
reference: Joi.string().required()
}).options({ stripUnknown: true })
const { error } = schema.validate(response)
if (error) {
throw new PaymailServerResponseError(`Validation error: ${error.message}`)
}
return response
}

/**
* Sends a transaction using the Pay-to-Peer (P2P) protocol.
* This method is used to send a transaction to a Paymail address.
Expand Down Expand Up @@ -222,6 +249,40 @@ export default class PaymailClient {
return value
}

/**
* Sends a transaction using the Pay-to-Peer (P2P) protocol.
* This method is used to send a transaction to a Paymail address.
*
* @param paymail - The Paymail address to send the transaction to.
* @param hex - The transaction in hexadecimal format.
* @param reference - A reference identifier for the transaction.
* @param metadata - Optional metadata for the transaction including sender, public key, signature, and note.
* @returns A Promise that resolves to an object containing the transaction ID and an optional note.
* @throws PaymailServerResponseError - Thrown if there is a validation error in the response.
*/
public sendOrdinalTransactionP2P = async (paymail: string, hex: string, reference: string, metadata?: {
sender: string
pubkey: string
signature: string
note: string
}) => {
const response = await this.request(paymail, SimpleP2pOrdinalReceiveCapability, {
hex,
reference,
metadata
})

const schema = Joi.object({
txid: Joi.string().required(),
note: Joi.string().optional().allow('')
}).options({ stripUnknown: true })
const { error, value } = schema.validate(response)
if (error) {
throw new PaymailServerResponseError(`Validation error: ${error.message}`)
}
return value
}

/**
* Creates a digital signature for a P2P transaction using a given private key.
* @param txid - The transaction ID to be signed.
Expand Down
49 changes: 49 additions & 0 deletions src/paymailRouter/__tests/simple-p2p-ordinal-destinations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import request from 'supertest'
import express from 'express'
import PaymailRouter from '../../../dist/cjs/src/paymailRouter/paymailRouter.js'
import OrdinalP2pPaymentDestinationRoute from '../../../dist/cjs/src/paymailRouter/paymailRoutes/simpleP2pOrdinalDestinationsRoute.js'
import PaymailClient from '../../../dist/cjs/src/paymailClient/paymailClient.js'

describe('#Paymail Server - Simple Ordinal P2P Payment Destinations', () => {
let app
let client: PaymailClient

beforeAll(() => {
app = express();
const baseUrl = 'http://localhost:3000';
client = new PaymailClient(undefined, undefined, undefined);
const routes = [
new OrdinalP2pPaymentDestinationRoute({
domainLogicHandler: (name, domain, body) => {
return {
outputs: [
{
script: '76a914f32281faa74e2ac037493f7a3cd317e8f5e9673688ac',
}
],
reference: 'someref'
};
}
})
];
const paymailRouter = new PaymailRouter({ baseUrl, routes });
app.use(paymailRouter.getRouter());
});

it('should get ordinal p2p destination', async () => {
const response = await request(app).post('/ordinal-p2p-payment-destination/satoshi@bsv.org').send({
ordinals: 1,
})
expect(response.statusCode).toBe(200)
expect(response.body.outputs[0].script).toEqual('76a914f32281faa74e2ac037493f7a3cd317e8f5e9673688ac')
expect(response.body.reference).toEqual('someref')
})

it('should return 400 if ordinals is not provided', async () => {
const response = await request(app).post('/ordinal-p2p-payment-destination/satoshi@bsv.org').send({
BSV: 1
})
expect(response.statusCode).toBe(400)
expect(response.error.text).toEqual('Invalid body: "ordinals" is required')
})
})
Loading

0 comments on commit ad3adeb

Please sign in to comment.