unisat-wallet / dev-support

30 stars 19 forks source link

API create bid with "nftAddress" param #73

Open adambor opened 2 weeks ago

adambor commented 2 weeks ago

If I try to to call the create bid endpoint for collections (https://open-api.unisat.io/v3/market/collection/auction/create_bid), with the following data:

{
    "auctionId": "ehv4oe9ev7ff4vm45xfbzjsijvqpmls9",
    "bidPrice": 18000,
    "address": "...",
    "pubkey": "...",
    "feeRate": 10,
    "utxos": [
        {
            "txid": "...",
            "index": 0
        }
    ],
    "nftAddress": "..."
}

I get a response saying:

{
    "code": -113,
    "msg": "If you use the receive address, you need to bind first",
    "data": null
}

What does this mean and how do I "bind first" through the API?

cloud6605 commented 1 week ago

You needn't set the nftAddress, it's only for multi-address wallet, such as Xverse, hiro. Please refer to https://docs.unisat.io/dev/unisat-developer-center/unisat-marketplace/brc20-marketplace to learn how to use the parameters.

image

Dill777 commented 1 week ago

I'm actually trying to do it too, but receiving problem:

{
  btcAddress: 'bc1q5fqwlf88dtu9qa2jkvwhjr94k07cerfau3mh9s',
  nftAddress: 'bc1p7n462ru9wtfgclwedfgp0eqe6w40pjwt7wvkxgdmzue2y6ptdkvsuzzmu2',
  sign: '6b96d3f0165b11b448163825f67a894e8cb1e2fcaa44f203ce6f704c77c84c1c3b73735840b75c25b9d8b11c1448785c5f10f0d5a746f0482b95a4d2fc9f94d3'
}
{
  code: -1,
  msg: 'Signature.fromCompact: Expected 64-byte hex',
  data: null
}

Can you help? @cloud6605

adambor commented 1 week ago

You needn't set the nftAddress, it's only for multi-address wallet, such as Xverse, hiro. Please refer to https://docs.unisat.io/dev/unisat-developer-center/unisat-marketplace/brc20-marketplace to learn how to use the parameters.

image

Well, I guess I need to use the nftAddress param... My use case:

A HD wallet with many different UTXOs across many different p2wpkh addresses for payments & single derived p2tr address for ordinals. So I need to pay for the ordinal with the p2wpkh utxos - using the "utxos" param (and receive change to p2wpkh change address - using the "address" param) & receive the ordinal to the single derived p2tr address - using the "nftAddress" param. How do I accomplish this with the API?

adambor commented 1 week ago

I'm actually trying to do it too, but receiving problem:

{
  btcAddress: 'bc1q5fqwlf88dtu9qa2jkvwhjr94k07cerfau3mh9s',
  nftAddress: 'bc1p7n462ru9wtfgclwedfgp0eqe6w40pjwt7wvkxgdmzue2y6ptdkvsuzzmu2',
  sign: '6b96d3f0165b11b448163825f67a894e8cb1e2fcaa44f203ce6f704c77c84c1c3b73735840b75c25b9d8b11c1448785c5f10f0d5a746f0482b95a4d2fc9f94d3'
}
{
  code: -1,
  msg: 'Signature.fromCompact: Expected 64-byte hex',
  data: null
}

Can you help? @cloud6605

What's the endpoint you are using for this? Any pointers to docs about this procedure?

Dill777 commented 1 week ago

I'm actually trying to do it too, but receiving problem:

{
  btcAddress: 'bc1q5fqwlf88dtu9qa2jkvwhjr94k07cerfau3mh9s',
  nftAddress: 'bc1p7n462ru9wtfgclwedfgp0eqe6w40pjwt7wvkxgdmzue2y6ptdkvsuzzmu2',
  sign: '6b96d3f0165b11b448163825f67a894e8cb1e2fcaa44f203ce6f704c77c84c1c3b73735840b75c25b9d8b11c1448785c5f10f0d5a746f0482b95a4d2fc9f94d3'
}
{
  code: -1,
  msg: 'Signature.fromCompact: Expected 64-byte hex',
  data: null
}

Can you help? @cloud6605

What's the endpoint you are using for this? Any pointers to docs about this procedure?

Bind btcAddress and nftAddress.

https://open-api.unisat.io/v3/market/brc20/auction/bind

adambor commented 1 week ago

I'm actually trying to do it too, but receiving problem:

{
  btcAddress: 'bc1q5fqwlf88dtu9qa2jkvwhjr94k07cerfau3mh9s',
  nftAddress: 'bc1p7n462ru9wtfgclwedfgp0eqe6w40pjwt7wvkxgdmzue2y6ptdkvsuzzmu2',
  sign: '6b96d3f0165b11b448163825f67a894e8cb1e2fcaa44f203ce6f704c77c84c1c3b73735840b75c25b9d8b11c1448785c5f10f0d5a746f0482b95a4d2fc9f94d3'
}
{
  code: -1,
  msg: 'Signature.fromCompact: Expected 64-byte hex',
  data: null
}

Can you help? @cloud6605

What's the endpoint you are using for this? Any pointers to docs about this procedure?

Bind btcAddress and nftAddress.

https://open-api.unisat.io/v3/market/brc20/auction/bind

Thank you! So that's the endpoint I am looking for! How are you producing the signature?

Dill777 commented 1 week ago

Thank you! So that's the endpoint I am looking for! How are you producing the signature?

const keyPair = ECPair.fromWIF(wif);

const { address: btcAddress } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey! });

console.log(keyPair.publicKey!.toString('hex'));

if (!btcAddress) {
  console.error('Failed to derive btcAddress from the provided private key.');
  process.exit(1);
}

const nftAddress: string = 'bc1p7n462ru9wtfgclwedfgp0eqe6w40pjwt7wvkxgdmzue2y6ptdkvsuzzmu2';

if (!nftAddress) {
  console.error('nftAddress must be provided.');
  process.exit(1);
}

// Prepare the message
const timestamp: number = Date.now();
const message: string = `Please confirm that
Payment Address: ${nftAddress}
Ordinals Address: ${btcAddress}`;

// Sign the message
let signature: Buffer;
try {
  signature = bitcoinMessage.sign(message, keyPair.privateKey!, keyPair.compressed);
} catch (error) {
  console.error('Error signing the message:', (error as Error).message);
  process.exit(1);
}

const signatureWithoutRecovery: Buffer = signature.slice(1);

// Convert the signature to base64
const signatureBase64: string = signatureWithoutRecovery.toString('hex');

console.log(signatureBase64.length);
console.log(signatureBase64);

@cloud6605 Am I right ?

but it's not working for me, so if you will manage it -> let me know please

adambor commented 1 week ago

Thank you! So that's the endpoint I am looking for! How are you producing the signature?

const keyPair = ECPair.fromWIF(wif);

const { address: btcAddress } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey! });

console.log(keyPair.publicKey!.toString('hex'));

if (!btcAddress) {
  console.error('Failed to derive btcAddress from the provided private key.');
  process.exit(1);
}

const nftAddress: string = 'bc1p7n462ru9wtfgclwedfgp0eqe6w40pjwt7wvkxgdmzue2y6ptdkvsuzzmu2';

if (!nftAddress) {
  console.error('nftAddress must be provided.');
  process.exit(1);
}

// Prepare the message
const timestamp: number = Date.now();
const message: string = `Please confirm that
Payment Address: ${nftAddress}
Ordinals Address: ${btcAddress}`;

// Sign the message
let signature: Buffer;
try {
  signature = bitcoinMessage.sign(message, keyPair.privateKey!, keyPair.compressed);
} catch (error) {
  console.error('Error signing the message:', (error as Error).message);
  process.exit(1);
}

const signatureWithoutRecovery: Buffer = signature.slice(1);

// Convert the signature to base64
const signatureBase64: string = signatureWithoutRecovery.toString('hex');

console.log(signatureBase64.length);
console.log(signatureBase64);

@cloud6605 Am I right ?

but it's not working for me, so if you will manage it -> let me know please

Based on the docs you should be signing by the ordinals address (p2tr), not by the payment address. Anyway I am trying to do that but also getting the same error as you do, so there might be some issue on the unisat's API side. Btw I am also using bitcoinjs-lib, but bip322-js lib for signing the message - you also should use that because bitcoinjs-message is 4 years old and doesn't support p2tr (and you need to sign by the ordinals p2tr address).

Dill777 commented 1 week ago

Based on the docs you should be signing by the ordinals address (p2tr), not by the payment address. Anyway I am trying to do that but also getting the same error as you do, so there might be some issue on the unisat's API side. Btw I am also using bitcoinjs-lib, but bip322-js lib for signing the message - you also should use that because bitcoinjs-message is 4 years old and doesn't support p2tr (and you need to sign by the ordinals p2tr address).

Could you share the code?

adambor commented 1 week ago

Could you share the code?

Here it is, just be sure to get the version 6.1.6 of the bitcoinjs-lib (7 doesn't work well for me for some reason), and version 2.1.0 of ecpair.

const bitcoin = require("bitcoinjs-lib"); //version 6.1.6
const ecc = require('@bitcoinerlab/secp256k1'); //version 1.1.1
const {ECPairFactory} = require("ecpair"); //version 2.1.0
const {Signer} =  require("bip322-js"); //version 2.0.0
const bip371 = require('bitcoinjs-lib/src/psbt/bip371');
bitcoin.initEccLib(ecc);

const ECPair = ECPairFactory(ecc);

const p2wpkhKey = ECPair.makeRandom();
const p2trKey = ECPair.makeRandom();

const p2wpkh = bitcoin.payments.p2wpkh({pubkey: p2wpkhKey.publicKey});
const p2tr = bitcoin.payments.p2tr({internalPubkey: bip371.toXOnly(p2trKey.publicKey)});

console.log("p2wpkh: ", p2wpkh.address);
console.log("p2tr: ", p2tr.address);

const toSignStr = `Please confirm that\nPayment Address: ${p2wpkh.address}\nOrdinals Address: ${p2tr.address}`;

const signatureBase64 = Signer.sign(p2trKey.toWIF(), p2tr.address, toSignStr);
console.log("Signature: ", signatureBase64);
console.log("Signature hex: ", Buffer.from(signatureBase64, 'base64').toString("hex"));

It prints out the signature in base64 and also in hex, you can get just the signature by stripping first 2 bytes (witness push count & signature push size) and the last 1 byte (sighash)

Dill777 commented 1 week ago

Could you share the code?

Here it is, just be sure to get the version 6.1.6 of the bitcoinjs-lib (7 doesn't work well for me for some reason), and version 2.1.0 of ecpair.

const bitcoin = require("bitcoinjs-lib"); //version 6.1.6
const ecc = require('@bitcoinerlab/secp256k1'); //version 1.1.1
const {ECPairFactory} = require("ecpair"); //version 2.1.0
const {Signer} =  require("bip322-js"); //version 2.0.0
const bip371 = require('bitcoinjs-lib/src/psbt/bip371');
bitcoin.initEccLib(ecc);

const ECPair = ECPairFactory(ecc);

const p2wpkhKey = ECPair.makeRandom();
const p2trKey = ECPair.makeRandom();

const p2wpkh = bitcoin.payments.p2wpkh({pubkey: p2wpkhKey.publicKey});
const p2tr = bitcoin.payments.p2tr({internalPubkey: bip371.toXOnly(p2trKey.publicKey)});

console.log("p2wpkh: ", p2wpkh.address);
console.log("p2tr: ", p2tr.address);

const toSignStr = `Please confirm that\nPayment Address: ${p2wpkh.address}\nOrdinals Address: ${p2tr.address}`;

const signatureBase64 = Signer.sign(p2trKey.toWIF(), p2tr.address, toSignStr);
console.log("Signature: ", signatureBase64);
console.log("Signature hex: ", Buffer.from(signatureBase64, 'base64').toString("hex"));

It prints out the signature in base64 and also in hex, you can get just the signature by stripping first 2 bytes (witness push count & signature push size) and the last 1 byte (sighash)

Maybe we can try with this Signer:

import {Signer as BTCSigner} from "bitcoinjs-lib/src/psbt";

Saw in some reps. You can check if you want. So you have the same error, yes ? it's really strange.

adambor commented 1 week ago

Maybe we can try with this Signer:

import {Signer as BTCSigner} from "bitcoinjs-lib/src/psbt";

That can only sign a specific hash, doesn't really help, because bip322 is about how you construct that hash (it is actually an invalid bitcoin transaction).

Saw in some reps. You can check if you want. So you have the same error, yes ? it's really strange.

Yep, if I send the hex output of the bip322-js library (full witness) I get:

{
    "btcAddress": "bc1q6rx2tx2f3q6g4khpqrzqlh549emrl5qakcx42e",
    "nftAddress": "bc1pgkrwm6c4g56l6jk4kpfar29nghjmzs7fmrdxmcxyk6a7dcuxlfxqd7emjc",
    "sign": "01419e2ddd587ff0e23b12ef176df1d96804dfa20f1da6438c389e40656e743ebeec71e57ae46f05d365fc466b7e261e61d08d46289a38f193fd483d82bbbfd6a84501"
}
{
    "code": -1,
    "msg": "Invalid padding: string should have whole number of bytes",
    "data": null
}

And if I send just the signature (strip first 2 bytes & last byte), I get:

{
    "btcAddress": "bc1q6rx2tx2f3q6g4khpqrzqlh549emrl5qakcx42e",
    "nftAddress": "bc1pgkrwm6c4g56l6jk4kpfar29nghjmzs7fmrdxmcxyk6a7dcuxlfxqd7emjc",
    "sign": "9e2ddd587ff0e23b12ef176df1d96804dfa20f1da6438c389e40656e743ebeec71e57ae46f05d365fc466b7e261e61d08d46289a38f193fd483d82bbbfd6a845"
}
{
    "code": -1,
    "msg": "Signature.fromCompact: Expected 64-byte hex",
    "data": null
}
adambor commented 1 week ago

I am also wondering why this binding procedure is even required (especially when used over the API), to me seems like just a UX hurdle.

Dill777 commented 1 week ago

I am also wondering why this binding procedure is even required (especially when used over the API), to me seems like just a UX hurdle.

Maybe @cloud6605 could help, for now I see only one way -> use Taproot address