Skip to content

Commit 9e1574d

Browse files
committed
feat: add bitcoin address types
1 parent 54ce84a commit 9e1574d

File tree

1 file changed

+82
-14
lines changed

1 file changed

+82
-14
lines changed

src/content/docs/protocol/v2/solver.mdx

+82-14
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ sidebar:
88
import { Tabs, TabItem } from '@astrojs/starlight/components';
99
import GetQuotes from '../../../../components/solver/GetQuotes.svelte';
1010

11-
If you aren't interested in the order structure in Solidity, skip to [From Vm](#from-vm).
11+
If you aren't interested in the order structure in Solidity, skip to [From Vm](#from-vm). For API documentation, refer to the [API Swagger documentation](https://catalyst-order-server-0140d799e2f7.herokuapp.com/api
12+
).
1213

1314
Catalyst is ERC-7683-ish compatible. The implementation differs in 2 ways:
1415

@@ -181,8 +182,6 @@ def get_orders():
181182
</TabItem>
182183
</Tabs>
183184

184-
<br/>
185-
186185
<GetQuotes client:visible/>
187186

188187
### Evaluate Orders
@@ -216,7 +215,8 @@ async function initiateOrder() {
216215

217216
// TODO: Set approvals for the reactorAddress for all inputs & collateral.
218217

219-
// The order arrives almost exactly ready to use, we just need to remove the type from the orderdata.
218+
// The order arrives almost exactly ready to use,
219+
// we just need to remove the type from the orderdata.
220220
const {type: _, ...cleanedOrderData} = order.order.orderData;
221221
const cleanedOrder = {...order.order, orderData: cleanedOrderData};
222222
const signature = order.signature;
@@ -252,7 +252,8 @@ def initiate_order():
252252
reactor = web3.eth.contract(address=reactor_address, abi=reactor_abi)
253253

254254
# TODO: Set approvals for the reactorAddress for all inputs & collateral.
255-
# This will depend on the specific ERC20 tokens you're using, you need to call approve() on the ERC20 token contracts
255+
# This will depend on the specific ERC20 tokens you're using,
256+
# you need to call approve() on the ERC20 token contracts
256257

257258
# Clean the order data by removing the type field
258259
cleaned_order_data = order['order']['orderData'].copy()
@@ -320,13 +321,13 @@ fillerData = fillerDataVersion + fillerAddress + orderPurchaseDeadline + orderDi
320321
</TabItem>
321322
</Tabs>
322323

323-
### Delivery
324+
## Delivery
324325

325326
Asset delivery depends on whether the destination is a VM chain or Bitcoin. For VM chains, you have to call the reactor with the appropriate configurations where for Bitcoin you just have to make any transfer that matches the provided arguments.
326327

327-
#### EVM deliveries
328+
### EVM deliveries
328329

329-
Call the destination chain oracle `getOrderData.order.orderData.remoteOracle`. You can get remote chain from the output `getOrderData.order.orderData.outputs[].chainId`.
330+
Call the destination chain oracle `getOrderData.order.orderData.remoteOracle`. You can get remote chain from each output `getOrderData.order.orderData.outputs[].chainId`.
330331

331332
<Tabs syncKey="lang">
332333
<TabItem label="Typescript">
@@ -337,16 +338,18 @@ import {ethers} from 'ethers';
337338

338339
const oracleAbi = "...";
339340

340-
// The oracle allows filling multiple outputs from different orders in a single transaction.
341-
// They do have to go to the same chain.
342-
// For simplicity, this function assumes that all outputs goes to the same chain but it may not be the case.
341+
// The oracle allows filling multiple outputs from different orders
342+
// in a single transaction. They do have to go to the same chain.
343+
// For simplicity, this function assumes that all outputs goes to
344+
// the same chain but it may not be the case.
343345
async function fillSingleChainOrder(order: CrossChainOrder) {
344346
const oracle = new ethers.Contract(order.orderData.remoteOracle, oracleAbi, signer);
345347

346348
let recordedChain;
347349
for (const output of order.orderData.outputs) {
348350
if (recordedChain === undefined) recordedChain = output.chainId;
349-
if (recordedChain !== output.chainId) throw Error(`Mixed ChainIds, seen ${recordedChain} and ${output.chainId}`);
351+
if (recordedChain !== output.chainId)
352+
throw Error(`Mixed ChainIds, seen ${recordedChain} and ${output.chainId}`);
350353
}
351354

352355
// TODO: Set approvals for the oracleAddress for the value of the output.
@@ -406,9 +409,74 @@ def fill_single_chain_order(order):
406409
</TabItem>
407410
</Tabs>
408411

409-
#### Bitcoin Deliveries
412+
### Bitcoin Deliveries
413+
414+
To identify wheter an order contains a Bitcoin transaction, check the output token: `getOrderData.order.orderData.outputs[].token`. If the output is Bitcoin, the following must hold:
415+
- The first 12 bytes can be anything and are not read.
416+
- The 13'th byte (or 20'th from right) must be 0xBB.
417+
- Byte 14 through 30 must be 0.
418+
- Byte The 2 bytes 31 and 32 contains an address version identifier. Decode as uint16.
419+
420+
If the transaction is to Bitcoin, the address (`getOrderData.order.orderData.outputs[].recipient`) will contain the relevant hash or witness.
421+
422+
<table>
423+
<thead>
424+
<tr>
425+
<th>Version</th>
426+
<th>Name</th>
427+
<th>Encoding Scheme</th>
428+
<th>Prefix</th>
429+
</tr>
430+
</thead>
431+
<tbody>
432+
<tr>
433+
<td>0</td>
434+
<td>Unknown</td>
435+
<td>Ignore</td>
436+
<td></td>
437+
</tr>
438+
<tr>
439+
<td>1</td>
440+
<td>P2PKH</td>
441+
<td>Base58Check(00+PKH)</td>
442+
<td>1*</td>
443+
</tr>
444+
<tr>
445+
<td>2</td>
446+
<td>P2SH</td>
447+
<td>Base58Check(05+SH)</td>
448+
<td>3*</td>
449+
</tr>
450+
<tr>
451+
<td>3</td>
452+
<td>P2WPKH</td>
453+
<td>Bech32</td>
454+
<td>b1cq</td>
455+
</tr>
456+
<tr>
457+
<td>4</td>
458+
<td>P2WSH</td>
459+
<td>Bech32</td>
460+
<td>b1cq</td>
461+
</tr>
462+
<tr>
463+
<td>5</td>
464+
<td>P2TR</td>
465+
<td>Bech32m</td>
466+
<td>b1cp</td>
467+
</tr>
468+
</tbody>
469+
</table>
470+
*Not true prefixes since the prefix is determined by the encoding.
471+
472+
The below tutorials assume that you are implementing this from the perspective of a solver. As a result, you are interested in converting an expected output script into a Bitcoin address that can be provided to your wallet.
473+
474+
- **P2PKH**. The recipient is the public key hash. The script hash needs to be encoded with [Base58Check](https://rosettacode.org/wiki/Base58Check_encoding). Select the first 20 bytes and prepend with 00. Encode with Base58Check.
475+
- **P2SH**. The recipient is the script hash. The script hash needs to be encoded with [Base58Check](https://rosettacode.org/wiki/Base58Check_encoding). Select the first 20 bytes and prepend with 05. Encode with Base58Check.
476+
- **P2WPKH**. The recipient is the witness. The witness needs to be encoded with [Bech32](https://github.com/bitcoinjs/bech32). Select the first 20 bytes. Encode with Bech32. Prepend with bc1q.
477+
- **P2WSH**. The recipient is the witness hash. The witness hash needs to be encoded with [Bech32](https://github.com/bitcoinjs/bech32). Select the first 32 bytes. Encode with Bech32. Prepend with bc1q.
478+
- **P2WTR**. The recipient is the witness hash. The witness hash needs to be encoded with [Bech32m](https://en.bitcoin.it/wiki/BIP_0350#Bech32m). Select the first 32 bytes. Encode with Bech32m. Prepend with bc1p.
410479

411-
The order contains a low level encoded version of the Bitcoin address. There exist 5 different types of Bitcoin scripts, P2PKH, P2SH, P2WPKH, P2WSH, and P2TR addresses using 3 different ways to encode the script into an address: P2PKH and P2SH uses `RIPEMD-160(SHA-256(pubkey/script))` with 1 and 3 prepended respectively, P2WPKH and P2WSH uses SHA256 encoded with Bech32 with either 20 or 32 bytes encoded respectively prepended with bc1 and lastly P2TR Bech32m encodes the output key directly.
412480

413481
## From Bitcoin
414482

0 commit comments

Comments
 (0)