// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
/******************************************************************************
 * Copyright © 2014-2020 The SuperNET Developers.                             *
 *                                                                            *
 * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at                  *
 * the top-level directory of this distribution for the individual copyright  *
 * holder information and the developer policies on copyright and licensing.  *
 *                                                                            *
 * Unless otherwise agreed in a custom licensing agreement, no part of the    *
 * SuperNET software, including this file may be copied, modified, propagated *
 * or distributed except according to the terms contained in the LICENSE file *
 *                                                                            *
 * Removal or modification of this copyright notice is prohibited.            *
 *                                                                            *
 ******************************************************************************/

// build hushdex and put in path: git pull; gcc cc/dapps/hushdex.c -lm -o hushdex; cp hushdex /usr/bin
// alice sends relcoin and gets basecoin

#define DEXP2P_CHAIN ((char *)"HUSHDEX")
#define DEXP2P_PUBKEYS ((char *)"hushdex")
#include "dappinc.h"

// for OTC mode, the following 4 functions are the only ones that should be needed to support a new "coin"
//int64_t hushdex_getbalance(char *coin);
//bits256 hushdex_coinpayment(int32_t OTCmode,char *coin,char *destaddr,uint64_t paytoshis,char *memostr);
//cJSON *hushdex_txidwait(char *coin,bits256 txid,char *hexstr,int32_t numseconds);
//int64_t hushdex_verifypayment(char *coin,cJSON *rawtx,uint64_t destsatoshis,char *destaddr);

// TODO:
// address conversion
// new inventory types:
//   anonsend

// bob nodes:
//   mutex for bob instances
//   "deposits" messages and approved bobs
//   volume caps per coin and non-notarized exposure

// later:
//   sharded storage

#define SUBATOMIC_OTCDEFAULT 1
#define SUBATOMIC_TIMEOUT 60
#define SUBATOMIC_LOCKTIME 3600
#define SUBATOMIC_TXFEE 10000
#define SUBATOMIC_PRIORITY 5
#define SUBATOMIC_OPENREQUEST 1
#define SUBATOMIC_APPROVED 2
#define SUBATOMIC_OPENED 3
#define SUBATOMIC_PAYMENT 4
#define SUBATOMIC_PAIDINFULL 5
#define SUBATOMIC_CLOSED 6

cJSON *SUBATOMIC_json;
int32_t SUBATOMIC_retval = -1;

struct abinfo
{
    char pubkey[67],recvaddr[64],recvZaddr[128],secp[67];
};

struct coininfo
{
    uint64_t satoshis,txfee,maxamount;
    char istoken,iszaddr,isfile,isexternal,tokenid[65],coin[16],name[16],cli[256],acname[16],coinstr[16];
};

struct msginfo
{
    UT_hash_handle hh;
    bits256 bobpayment,alicepayment;
    double price;
    uint64_t gotpayment;
    uint32_t origid,openrequestid,approvalid,openedid,paymentids[100],paidid,closedid,locktime;
    int32_t bobflag,status,OTCmode;
    char payload[128],approval[128],senderpub[67],msigaddr[64],redeemscript[256];
    struct coininfo base,rel;
    struct abinfo alice,bob;
} *Messages;

uint64_t hushdex_txfee(char *coin)
{
    return(SUBATOMIC_TXFEE);
}

char *hushdex_checkname(char *tmpstr,struct msginfo *mp,int32_t baserel,char *coin)
{
    int32_t i,n; cJSON *external,*item; char *coinstr,*clistr; struct coininfo *ptr;
    ptr = (baserel == 0) ? &mp->base : &mp->rel;
    if ( coin[0] == 0 )
        return(coin);
    if ( (external= jarray(&n,SUBATOMIC_json,"externalcoins")) != 0 && n > 0 )
    {
        for (i=0; i<n; i++)
        {
            item = jitem(external,i);
            if ( (clistr= jstr(item,coin)) != 0 && strlen(clistr) < sizeof(ptr->cli) )
            {
                ptr->isexternal = 1;
                strcpy(ptr->cli,clistr);
                //fprintf(stderr,"found external coin %s %s\n",coin,clistr);
            }
        }
    }
    if ( coin[0] == '#' )
    {
        strcpy(ptr->coinstr,coin);
        strcpy(ptr->acname,"");
        ptr->isfile = 1;
        return(coin);
    }
    else if ( coin[0] != 'z' )
    {
        for (i=1; coin[i]!=0; i++)
            if ( coin[i] == '.' )
            {
                dpow_tokenregister(ptr->tokenid,0,coin,0);
                if ( ptr->tokenid[0] != 0 )
                {
                    strcpy(tmpstr,coin);
                    tmpstr[i] = 0;
                    //fprintf(stderr,"found a tokenmap %s -> %s %s\n",coin,tmpstr,ptr->tokenid);
                    ptr->istoken = 1;
                    strcpy(ptr->acname,coin);
                    strcpy(ptr->coinstr,"");
                    return(tmpstr);
                }
            }
        if ( ptr->isexternal == 0 )
        {
                strcpy(ptr->acname,coin);
                strcpy(ptr->coinstr,"");
                strcpy(ptr->acname,"");
        }
        else
        {
            strcpy(ptr->coinstr,coin);
            strcpy(ptr->acname,"");
        }
        return(coin);
    }
    else
    {
        for (i=1; coin[i]!=0; i++)
            if ( isupper(coin[i]) == 0 )
                return(coin);
            ptr->iszaddr = 1;
        return(coin+1);
    }
}

int32_t hushdex_zonly(struct coininfo *coin)
{
    if ( strcmp(coin->coin,"HUSH3") == 0 )
        return(1);

    if ( strcmp(coin->coin,"HUSHFILE") == 0 )
        return(1);

    return 0;
}

// //////////////////////////////// the four key functions needed to support a new item for hushdexs

int64_t _hushdex_getbalance(struct coininfo *coin)
{
    cJSON *retjson; char *retstr,cmpstr[64]; int64_t amount=0;
    if ( (retjson= hushdex_cli(coin->cli,&retstr,"getbalance","","","","","","","")) != 0 )
    {
        fprintf(stderr,"_hushdex_getbalance.(%s) %s returned json!\n",coin->coinstr,coin->cli);
        free_json(retjson);
    }
    else if ( retstr != 0 )
    {
        amount = atof(retstr) * SATOSHIDEN;
        sprintf(cmpstr,"%.8f",dstr(amount));
        if ( strcmp(retstr,cmpstr) != 0 )
            amount++;
        //printf("retstr %s -> %.8f\n",retstr,dstr(amount));
        free(retstr);
    }
    return (amount);
}

bits256 _hushdex_sendtoaddress(struct coininfo *coin,char *destaddr,int64_t satoshis)
{
    char numstr[32],*retstr,str[65]; cJSON *retjson; bits256 txid;
    memset(txid.bytes,0,sizeof(txid));
    sprintf(numstr,"%.8f",(double)satoshis/SATOSHIDEN);
    if ( (retjson= hushdex_cli(coin->cli,&retstr,"sendtoaddress",destaddr,numstr,"false","","","","")) != 0 )
    {
        fprintf(stderr,"unexpected _hushdex_sendtoaddress json.(%s)\n",jprint(retjson,0));
        free_json(retjson);
    }
    else if ( retstr != 0 )
    {
        if ( strlen(retstr) >= 64 )
        {
            retstr[64] = 0;
            decode_hex(txid.bytes,32,retstr);
        }
        fprintf(stderr,"_hushdex_sendtoaddress %s %.8f txid.(%s)\n",destaddr,(double)satoshis/SATOSHIDEN,bits256_str(str,txid));
        free(retstr);
    }
    return(txid);
}

cJSON *_hushdex_rawtransaction(struct coininfo *coin,bits256 txid)
{
    cJSON *retjson; char *retstr,str[65];
    if ( (retjson= hushdex_cli(coin->cli,&retstr,"getrawtransaction",bits256_str(str,txid),"1","","","","","")) != 0 )
    {
        return(retjson);
    }
    else if ( retstr != 0 )
    {
        fprintf(stderr,"_hushdex_rawtransaction.(%s) %s error.(%s)\n",coin->coin,coin->name,retstr);
        free(retstr);
    }
    return(0);
}

int64_t hushdex_getbalance(struct coininfo *coin)
{
    char *coinstr,*acname=""; FILE *fp; int64_t retval = 0;
    coinstr = coin->coin;
    if ( coin->isfile != 0 )
    {
        if ( (fp= fopen(coin->name+1,"rb")) != 0 ) // if alice, add bob pubkey to fname
        {
            fclose(fp);
            retval = SATOSHIDEN;
        }
        return(retval);
    } else if ( hushdex_zonly(coin) != 0 ) {
        return(z_getbalance(coinstr,acname,DPOW_recvZaddr));
    }
}

bits256 hushdex_coinpayment(uint32_t origid,int32_t OTCmode,struct coininfo *coin,char *destaddr,uint64_t paytoshis,char *memostr,char *destpub,char *senderpub)
{
    bits256 txid; char opidstr[128],opretstr[32],str[65],*status,*coinstr,*acname=""; cJSON *retjson,*retjson2,*item,*res; int32_t i,pending=0;
    memset(&txid,0,sizeof(txid));
    if ( OTCmode == 0 )
    {
        fprintf(stderr,"micropayment channels are not supported yet\n");
        return(txid);
    }
    if ( coin->isfile != 0 )
    {
        fprintf(stderr,"start broadcast of (%s)\n",coin->coin+1);
        if ( (retjson= dpow_publish(SUBATOMIC_PRIORITY,coin->coin+1)) != 0 ) // spawn thread
        {
            sprintf(opretstr,"%08x",juint(retjson,"id"));
            sprintf(opidstr,"%u",origid);
            if ( (retjson2= dpow_broadcast(SUBATOMIC_PRIORITY,opretstr,"inbox",opidstr,senderpub,"","")) != 0 )
                free_json(retjson2);
            fprintf(stderr,"broadcast file.(%s) and send id.%u to alice (%s)\n",coin->coin+1,juint(retjson,"id"),jprint(retjson,0));
            txid = jbits256(retjson,"filehash");
            free_json(retjson);
        }
        fprintf(stderr,"end broadcast of (%s) to %s\n",coin->coin+1,senderpub);
        return(txid);
    }
    else if ( hushdex_zonly(coin) != 0 )
    {
        if ( memostr[0] == 0 )
            memostr = "beef";
        z_sendmany(opidstr,"",coin->coin,DPOW_recvZaddr,destaddr,paytoshis,memostr);
        for (i=0; i<SUBATOMIC_TIMEOUT; i++)
        {
            if ( (retjson= z_getoperationstatus("",coin->coin,opidstr)) != 0 )
            {
                item = jitem(retjson,0);
                if ( (status= jstr(item,"status")) != 0 )
                {
                    if ( strcmp(status,"executing") == 0 )
                        pending++;
                    else
                    {
                        res = jobj(item,"result");
                        txid = jbits256(res,"txid");
                        //fprintf(stderr,"got Ztx txid.%s\n",bits256_str(str,txid));
                        free_json(retjson);
                        break;
                    }
                    /*else if ( clearresults != 0 )
                     {
                     if ( (result= z_getoperationresult(coinstr,"",jstri(array,i))) != 0 )
                     {
                     free_json(result);
                     }
                     }*/
                }
                free_json(retjson);
            }
            sleep(1);
        }
        if ( i == 60 )
            printf("%u timed out waiting for opid to finish\n",origid);
    }
    else
    {
        coinstr = coin->coin;
        if ( coin->istoken != 0 )
            txid = tokentransfer(coinstr,acname,coin->tokenid,destpub,paytoshis/SATOSHIDEN);
        else if ( coin->isexternal == 0 )
        {
            sprintf(opretstr,"%08x",origid);
            txid = sendtoaddress(coinstr,acname,destaddr,paytoshis,opretstr);
        } else txid = _hushdex_sendtoaddress(coin,destaddr,paytoshis);
        printf("%u got txid.%s\n",origid,bits256_str(str,txid));
    }
    return(txid);
}

cJSON *hushdex_txidwait(struct coininfo *coin,bits256 txid,char *hexstr,int32_t numseconds,char *senderpub)
{
    int32_t i,zflag; char *coinstr,str[65],*acname=""; cJSON *rawtx; bits256 z; bits256 filehash;
    memset(&z,0,sizeof(z));
    if ( memcmp(&z,&txid,sizeof(txid)) == 0 )
        return(0);
    if ( hexstr != 0 && hexstr[0] != 0 ) // probably not worth doing and zaddr is a problem to decode
    {
        // compare against txid
        // if matches, sendrawtransaction if OTC mode, decoode and return if channels mode
    }
    zflag = (hushdex_zonly(coin) != 0);
    coinstr = coin->coin;
    for (i=0; i<numseconds; i++)
    {
        if ( coin->isfile != 0 )
        {
            if ( (rawtx= dpow_subscribe(SUBATOMIC_PRIORITY,coin->coin+1,senderpub)) != 0 )
            {
                filehash = jbits256(rawtx,"filehash");
                if ( memcmp(&filehash,&txid,sizeof(filehash)) != 0 )
                {
                    fprintf(stderr,"waiting (%s) (%s)\n",coin->coin+1,jprint(rawtx,0));
                    free_json(rawtx);
                    rawtx = 0;
                } else return(rawtx);
            }
        }
        else if ( zflag != 0 )
            rawtx = get_z_viewtransaction(coinstr,acname,txid);
        else if ( coin->isexternal == 0 )
            rawtx = get_rawtransaction(coinstr,acname,txid);
        else rawtx = _hushdex_rawtransaction(coin,txid);
        if ( rawtx != 0 )
            return(rawtx);
        sleep(1);
    }
    printf("%s/%s timeout waiting for %s\n",coin->name,coin->coin,bits256_str(str,txid));
    return(0);
}

int64_t hushdex_verifypayment(struct coininfo *coin,cJSON *rawtx,uint64_t destsatoshis,char *destaddr,bits256 txid)
{
    int32_t i,n,m,valid=0; bits256 tokenid,filehash,checkhash; cJSON *array,*item,*sobj,*a; char *addr,*acname,*coinstr,tokenaddr[64],*hex; uint8_t hexbuf[512],pub33[33]; uint64_t netval,recvsatoshis = 0;
    if ( coin->isfile != 0 )
    {
        filehash = jbits256(rawtx,"filehash");
        checkhash = jbits256(rawtx,"checkhash");
        if ( memcmp(&txid,&filehash,sizeof(txid)) == 0 && memcmp(&txid,&checkhash,sizeof(txid)) == 0 )
        {
            fprintf(stderr,"verified file is matching the filehash (%s)\n",jprint(rawtx,0));
            return(SATOSHIDEN);
        } else return(0);
    }
    else if ( hushdex_zonly(coin) != 0 )
    {
        if ( (array= jarray(&n,rawtx,"outputs")) != 0 && n > 0 )
        {
            for (i=0; i<n; i++)
            {
                item = jitem(array,i);
                if ( (addr= jstr(item,"address")) != 0 && strcmp(addr,destaddr) == 0 )
                    recvsatoshis += j64bits(item,"valueZat");
            }
        }
    }
    else if ( coin->istoken != 0 )
    {
        if ( (array= jarray(&n,rawtx,"vout")) != 0 && n > 0 )
        {
            item = jitem(array,0);
            if ( (sobj= jobj(item,"scriptPubKey")) != 0 && (a= jarray(&m,sobj,"addresses")) != 0 && m == 1 )
            {
                coinstr = coin->coin;
                if ( get_tokenaddress(coinstr,acname,tokenaddr) != 0 )
                {
                    //fprintf(stderr,"tokenaddr.%s\n",tokenaddr);
                    if ( (addr= jstri(a,0)) != 0 && strcmp(addr,tokenaddr) == 0 )
                        recvsatoshis += SATOSHIDEN * (uint64_t)(jdouble(item,"value")*SATOSHIDEN + 0.000000004999);
                    else fprintf(stderr,"miscompare (%s) vs %s\n",jprint(sobj,0),addr);
                }
            }
            item = jitem(array,n-1);
            if ( (sobj= jobj(item,"scriptPubKey")) != 0 && (hex= jstr(sobj,"hex")) != 0 && (m= is_hexstr(hex,0)) > 1 && m/2 < sizeof(hexbuf) )
            {
                m >>= 1;
                decode_hex(hexbuf,m,hex);
                decode_hex(tokenid.bytes,32,coin->tokenid);
                decode_hex(pub33,33,DPOW_secpkeystr);
                // opret 69len EVAL_TOKENS 't' tokenid 1 33 pub33
                if ( hexbuf[0] == 0x6a && hexbuf[1] == 0x45 && hexbuf[2] == 0xf2 && hexbuf[3] == 't' && memcmp(&hexbuf[4],&tokenid,sizeof(tokenid)) == 0 && hexbuf[4+32] == 1 && hexbuf[4+32+1] == 33 && memcmp(&hexbuf[4+32+2],pub33,33) == 0 )
                {
                    valid = 1;
                    //fprintf(stderr,"validated it is a token transfer!\n");
                } else fprintf(stderr,"need to validate tokentransfer.(%s) %s %d\n",hex,DPOW_secpkeystr,memcmp(&hexbuf[4+32+2],pub33,33) == 0);
                //6a 45 f2 74 2b1feef719ecb526b07416dd432bce603ac6dc8bfe794cddf105cb52f6aae3cd 01 21 02b27de3ee5335518b06f69f4fbabb029cfc737613b100996841d5532b324a5a61
                
            }
            recvsatoshis *= valid;
        }
    }
    else
    {
        if ( (array= jarray(&n,rawtx,"vout")) != 0 && n > 0 )
        {
            for (i=0; i<n; i++)
            {
                item = jitem(array,i);
                if ( (sobj= jobj(item,"scriptPubKey")) != 0 && (a= jarray(&m,sobj,"addresses")) != 0 && m == 1 )
                {
                    if ( (addr= jstri(a,0)) != 0 && strcmp(addr,destaddr) == 0 )
                        recvsatoshis += (uint64_t)(jdouble(item,"value")*SATOSHIDEN + 0.000000004999);
                }
            }
        }
    }
    printf("%s received %.8f vs %.8f\n",destaddr,dstr(recvsatoshis),dstr(destsatoshis));
    return(recvsatoshis - destsatoshis);
}
// //////////// end

struct msginfo *hushdex_find(uint32_t origid)
{
    struct msginfo *mp;
    HASH_FIND(hh,Messages,&origid,sizeof(origid),mp);
    return(mp);
}

struct msginfo *hushdex_add(uint32_t origid)
{
    struct msginfo *mp = calloc(1,sizeof(*mp));
    mp->origid = origid;
    HASH_ADD(hh,Messages,origid,sizeof(origid),mp);
    return(mp);
}

int32_t hushdex_status(struct msginfo *mp,int32_t status)
{
    static FILE *fp;
    if ( fp == 0 )
    {
        int32_t i,oid,s,n,num,count; struct msginfo *m; long fsize;
        if ( (fp= fopen("SUBATOMIC.DB","rb+")) == 0 )
        {
            if ( (fp= fopen("SUBATOMIC.DB","wb")) == 0 )
            {
                fprintf(stderr,"cant create SUBATOMIC.DB\n");
                exit(-1);
            }
        }
        else
        {
            fseek(fp,0,SEEK_END);
            fsize = ftell(fp);
            if ( (fsize % (sizeof(uint32_t)*2)) != 0 )
            {
                fprintf(stderr,"SUBATOMIC.DB illegal filesize.%ld\n",fsize);
                exit(-1);
            }
            n = (int32_t)(fsize / (sizeof(uint32_t)*2));
            rewind(fp);
            for (i=num=count=0; i<n; i++)
            {
                if ( fread(&oid,1,sizeof(oid),fp) != sizeof(oid) || fread(&s,1,sizeof(s),fp) != sizeof(s) )
                {
                    fprintf(stderr,"SUBATOMIC.DB corrupted at filepos.%ld\n",ftell(fp));
                    exit(-1);
                }
                if ( s < 0 || s > SUBATOMIC_CLOSED )
                {
                    fprintf(stderr,"SUBATOMIC.DB corrupted at filepos.%ld: illegal status.%d\n",ftell(fp),s);
                    exit(-1);
                }
                //fprintf(stderr,"%u <- %d\n",oid,s);
                if ( (m= hushdex_find(oid)) == 0 )
                {
                    m = hushdex_add(oid);
                    count++;
                }
                if ( s > m->status )
                {
                    m->status = s;
                    num++;
                }
            }
            fprintf(stderr,"initialized %d messages, updated %d out of total.%d\n",count,num,n);
        }
    }
    if ( mp->status >= status )
        return(-1);
    if ( fwrite(&mp->origid,1,sizeof(mp->origid),fp) != sizeof(mp->origid) || fwrite(&status,1,sizeof(status),fp) != sizeof(status) )
        fprintf(stderr,"error updating SUBATOMIC.DB, risk of double spends\n");
    fflush(fp);
    mp->status = status;
    return(0);
}

struct msginfo *hushdex_tracker(uint32_t origid)
{
    struct msginfo *mp;
    if ( (mp= hushdex_find(origid)) == 0 )
    {
        mp = hushdex_add(origid);
        hushdex_status(mp,0);
    }
    return(mp);
}

char *hushdex_hexstr(char *jsonstr)
{
    char *hexstr; int32_t i,c,n = (int32_t)strlen(jsonstr);
    hexstr = malloc(2*n + 3);
    strcpy(hexstr,jsonstr);
    for (i=0; i<n; i++)
    {
        if ( (c= jsonstr[i]) == '"' )
            c = '\'';
        sprintf(&hexstr[i << 1],"%02x",c);
    }
    sprintf(&hexstr[i << 1],"00");
    i++;
    hexstr[i << 1] = 0;
    return(hexstr);
}

cJSON *hushdex_mpjson(struct msginfo *mp)
{
    cJSON *item;
    item = cJSON_CreateObject();
    jaddnum(item,"origid",mp->origid);
    jaddnum(item,"price",mp->price);
    jaddnum(item,"openrequest",mp->openrequestid);
    jaddstr(item,"base",mp->base.name);
    jaddstr(item,"basecoin",mp->base.coin);
    jadd64bits(item,"basesatoshis",mp->base.satoshis);
    jadd64bits(item,"basetxfee",mp->base.txfee);
    jadd64bits(item,"maxbaseamount",mp->base.maxamount);
    jaddstr(item,"rel",mp->rel.name);
    jaddstr(item,"relcoin",mp->rel.coin);
    jadd64bits(item,"relsatoshis",mp->rel.satoshis);
    jadd64bits(item,"reltxfee",mp->rel.txfee);
    jadd64bits(item,"maxrelamount",mp->rel.maxamount);
    jaddstr(item,"alice",mp->alice.pubkey);
    jaddstr(item,"alicesecp",mp->alice.secp);
    jaddstr(item,"bob",mp->bob.pubkey);
    jaddstr(item,"bobsecp",mp->bob.secp);
    if ( hushdex_zonly(&mp->rel) != 0 )
        jaddstr(item,"bobZaddr",mp->bob.recvZaddr);
    else jaddstr(item,"bobaddr",mp->bob.recvaddr);
    if ( mp->rel.istoken != 0 )
        jaddstr(item,"bobtoken",mp->rel.tokenid);
    if ( hushdex_zonly(&mp->base) != 0 )
        jaddstr(item,"aliceZaddr",mp->alice.recvZaddr);
    else jaddstr(item,"aliceaddr",mp->alice.recvaddr);
    if ( mp->base.istoken != 0 )
        jaddstr(item,"alicetoken",mp->base.tokenid);
    return(item);
}

uint64_t hushdex_orderbook_mpset(struct msginfo *mp,char *basecheck)
{
    cJSON *retjson; char *tagA,*tagB,*senderpub,*str,tmpstr[32]; int32_t matches=0; double volA,volB; int64_t txfee=0;
    strcpy(mp->base.name,basecheck);
    strcpy(mp->base.coin,hushdex_checkname(tmpstr,mp,0,basecheck));
    mp->rel.txfee = hushdex_txfee(mp->rel.coin);
    if ( (retjson= dpow_get(mp->origid)) != 0 )
    {
        //fprintf(stderr,"dpow_get.(%s) (%s/%s)\n",jprint(retjson,0),mp->base.coin,mp->rel.coin);
        if ( (senderpub= jstr(retjson,"senderpub")) != 0 && is_hexstr(senderpub,0) == 66 && (tagA= jstr(retjson,"tagA")) != 0 && (tagB= jstr(retjson,"tagB")) != 0 && strncmp(tagB,mp->rel.name,strlen(mp->rel.name)) == 0 && strlen(tagA) < sizeof(mp->base.name) )
        {
            strcpy(mp->base.name,tagA);
            strcpy(mp->base.coin,hushdex_checkname(tmpstr,mp,0,tagA));
            if ( basecheck[0] == 0 || strncmp(basecheck,tagA,strlen(basecheck)) == 0 )
                matches = 1;
            else if ( strcmp(tagA,mp->base.name) == 0 )
                matches = 1;
            else if ( mp->bobflag != 0 && tagA[0] == '#' && strcmp(mp->base.name,"#allfiles") == 0 )
                matches = 1;
            if ( matches != 0 )
            {
                if ( (str= jstr(retjson,"decrypted")) != 0 && strlen(str) < 128 )
                    strcpy(mp->payload,str);
                mp->locktime = juint(retjson,"timestamp") + SUBATOMIC_LOCKTIME;
                mp->base.txfee = hushdex_txfee(mp->base.coin);
                strcpy(mp->senderpub,senderpub);
                volB = jdouble(retjson,"amountB");
                volA = jdouble(retjson,"amountA");
                mp->base.maxamount = volA*SATOSHIDEN + 0.0000000049999;
                mp->rel.maxamount = volB*SATOSHIDEN + 0.0000000049999;
                if ( 0 && mp->rel.istoken == 0 )
                    txfee = mp->rel.txfee;
                if ( mp->base.maxamount != 0 && mp->rel.maxamount != 0 && volA > SMALLVAL && volB > SMALLVAL && mp->rel.satoshis <= mp->rel.maxamount )
                {
                    mp->price = volA / volB;
                    mp->base.satoshis = (mp->rel.satoshis - txfee) * mp->price;
                    //fprintf(stderr,"base satoshis.%llu\n",(long long)mp->base.satoshis);
                } else fprintf(stderr,"%u rel %llu vs (%llu %llu)\n",mp->origid,(long long)mp->rel.satoshis,(long long)mp->base.maxamount,(long long)mp->rel.maxamount);
            } else printf("%u didnt match (%s) tagA.%s %s, tagB.%s %s %d %d\n",mp->origid,basecheck,tagA,mp->base.name,tagB,mp->rel.name,tagA[0] == '#', strcmp(mp->base.name,"#allfiles") == 0);
        } else printf("%u didnt compare tagA.%s %s, tagB.%s %s\n",mp->origid,tagA,mp->base.name,tagB,mp->rel.name);
        free_json(retjson);
    }
    return(mp->base.satoshis);
}

char *randhashstr(char *str)
{
    bits256 rands; int32_t i;
    for (i=0; i<32; i++)
        rands.bytes[i] = rand() >> 17;
    bits256_str(str,rands);
    return(str);
}

void hushdex_extrafields(cJSON *dest,cJSON *src)
{
    char *str;
    if ( (str= jstr(src,"approval")) != 0 )
        jaddstr(dest,"approval",str);
    if ( (str= jstr(src,"opened")) != 0 )
        jaddstr(dest,"opened",str);
    if ( (str= jstr(src,"payamount")) != 0 )
        jaddstr(dest,"payamount",str);
    if ( (str= jstr(src,"destaddr")) != 0 )
        jaddstr(dest,"destaddr",str);
    if ( (str= jstr(src,"bobpayment")) != 0 )
        jaddstr(dest,"bobpayment",str);
    if ( (str= jstr(src,"alicepayment")) != 0 )
        jaddstr(dest,"alicepayment",str);
    if ( (str= jstr(src,"bobaddr")) != 0 )
        jaddstr(dest,"bobaddr",str);
    if ( (str= jstr(src,"bobZaddr")) != 0 )
        jaddstr(dest,"bobZaddr",str);
    if ( (str= jstr(src,"aliceaddr")) != 0 )
        jaddstr(dest,"aliceaddr",str);
    if ( (str= jstr(src,"aliceZaddr")) != 0 )
        jaddstr(dest,"aliceZaddr",str);
    if ( (str= jstr(src,"alicetoken")) != 0 )
        jaddstr(dest,"alicetoken",str);
    if ( (str= jstr(src,"bobtoken")) != 0 )
        jaddstr(dest,"bobtoken",str);
}

char *hushdex_submit(cJSON *argjson,int32_t tobob)
{
    char *jsonstr,*hexstr;
    jaddnum(argjson,"tobob",tobob != 0);
    jsonstr = jprint(argjson,1);
    hexstr = hushdex_hexstr(jsonstr);
    free(jsonstr);
    return(hexstr);
}

#define SCRIPT_OP_IF 0x63
#define SCRIPT_OP_ELSE 0x67
#define SCRIPT_OP_DUP 0x76
#define SCRIPT_OP_ENDIF 0x68
#define SCRIPT_OP_TRUE 0x51
#define SCRIPT_OP_2 0x52
#define SCRIPT_OP_3 0x53
#define SCRIPT_OP_DROP 0x75
#define SCRIPT_OP_EQUALVERIFY 0x88
#define SCRIPT_OP_HASH160 0xa9
#define SCRIPT_OP_EQUAL 0x87
#define SCRIPT_OP_CHECKSIG 0xac
#define SCRIPT_OP_CHECKMULTISIG 0xae
#define SCRIPT_OP_CHECKMULTISIGVERIFY 0xaf
#define SCRIPT_OP_CHECKLOCKTIMEVERIFY 0xb1

int32_t hushdex_redeemscript(char *redeemscript,uint32_t locktime,char *pubkeyA,char *pubkeyB) // not needed
{
    // if ( refund ) OP_HASH160 <2of2 multisig hash> OP_EQUAL   // standard multisig
    // else <locktime> CLTV OP_DROP <pubkeyA> OP_CHECKSIG // standard spend
    uint8_t pubkeyAbytes[33],pubkeyBbytes[33],hex[4096]; int32_t i,n = 0;
    decode_hex(pubkeyAbytes,33,pubkeyA);
    decode_hex(pubkeyBbytes,33,pubkeyB);
    hex[n++] = SCRIPT_OP_IF;
    hex[n++] = SCRIPT_OP_2;
    hex[n++] = 33, memcpy(&hex[n],pubkeyAbytes,33), n += 33;
    hex[n++] = 33, memcpy(&hex[n],pubkeyBbytes,33), n += 33;
    hex[n++] = SCRIPT_OP_2;
    hex[n++] = SCRIPT_OP_CHECKMULTISIG;
    hex[n++] = SCRIPT_OP_ELSE;
    hex[n++] = 4;
    hex[n++] = locktime & 0xff, locktime >>= 8;
    hex[n++] = locktime & 0xff, locktime >>= 8;
    hex[n++] = locktime & 0xff, locktime >>= 8;
    hex[n++] = locktime & 0xff;
    hex[n++] = SCRIPT_OP_CHECKLOCKTIMEVERIFY;
    hex[n++] = SCRIPT_OP_DROP;
    hex[n++] = 33; memcpy(&hex[n],pubkeyAbytes,33); n += 33;
    hex[n++] = SCRIPT_OP_CHECKSIG;
    hex[n++] = SCRIPT_OP_ENDIF;
    for (i=0; i<n; i++)
    {
        redeemscript[i*2] = hexbyte((hex[i]>>4) & 0xf);
        redeemscript[i*2 + 1] = hexbyte(hex[i] & 0xf);
    }
    redeemscript[n*2] = 0;
    /*tmpbuf[0] = SCRIPT_OP_HASH160;
     tmpbuf[1] = 20;
     calc_OP_HASH160(scriptPubKey,tmpbuf+2,redeemscript);
     tmpbuf[22] = SCRIPT_OP_EQUAL;
     init_hexbytes_noT(scriptPubKey,tmpbuf,23);
     if ( p2shaddr != 0 )
     {
     p2shaddr[0] = 0;
     if ( (btc_addr= base58_encode_check(addrtype,true,tmpbuf+2,20)) != 0 )
     {
     if ( strlen(btc_addr->str) < 36 )
     strcpy(p2shaddr,btc_addr->str);
     cstr_free(btc_addr,true);
     }
     }*/
    return(n);
}

int32_t hushdex_approved(struct msginfo *mp,cJSON *approval,cJSON *msgjson,char *senderpub)
{
    char *hexstr,numstr[32],redeemscript[1024],*coin,*acname=""; cJSON *retjson,*decodejson; int32_t i,retval = 0;
    hushdex_extrafields(approval,msgjson);
    if ( mp->OTCmode == 0 )
    {
        coin = (mp->bobflag != 0) ? mp->base.coin : mp->rel.coin; // the other side gets this coin
        if ( get_createmultisig2(coin,acname,mp->msigaddr,mp->redeemscript,mp->alice.secp,mp->bob.secp) != 0 )
        {
            hushdex_redeemscript(redeemscript,mp->locktime,mp->alice.secp,mp->bob.secp);
            if ( (decodejson= get_decodescript(coin,acname,redeemscript)) != 0 )
            {
                fprintf(stderr,"%s %s msigaddr.%s %s -> %s %s\n",mp->bobflag!=0?"bob":"alice",(mp->bobflag != 0) ? mp->base.coin : mp->rel.coin,mp->msigaddr,mp->redeemscript,redeemscript,jprint(decodejson,0));
                free(decodejson);
            }
        }
    }
    sprintf(numstr,"%u",mp->origid);
    for (i=0; numstr[i]!=0; i++)
        sprintf(&mp->approval[i<<1],"%02x",numstr[i]);
    sprintf(&mp->approval[i<<1],"%02x",' ');
    i++;
    mp->approval[i<<1] = 0;
    jaddstr(approval,"approval",mp->approval);
    hexstr = hushdex_submit(approval,!mp->bobflag);
    if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"approved",senderpub,"","")) != 0 )
    {
        if ( (mp->approvalid= juint(retjson,"id")) != 0 )
            retval = 1;
        printf("%u approvalid.%u (%s)\n",mp->origid,mp->approvalid,senderpub);
        hushdex_status(mp,SUBATOMIC_APPROVED);
        free_json(retjson);
    }
    free(hexstr);
    return(retval);
}

int32_t hushdex_opened(struct msginfo *mp,cJSON *opened,cJSON *msgjson,char *senderpub)
{
    char *hexstr,channelstr[65]; cJSON *retjson; int32_t retval = 0;
    hushdex_extrafields(opened,msgjson);
    jaddstr(opened,"opened",randhashstr(channelstr));
    hexstr = hushdex_submit(opened,!mp->bobflag);
    if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"opened",senderpub,"","")) != 0 )
    {
        if ( (mp->openedid= juint(retjson,"id")) != 0 )
            retval = 1;
        printf("%u openedid.%u\n",mp->origid,mp->openedid);
        hushdex_status(mp,SUBATOMIC_OPENED);
        free_json(retjson);
    }
    free(hexstr);
    return(retval);
}

int32_t hushdex_payment(struct msginfo *mp,cJSON *payment,cJSON *msgjson,char *senderpub)
{
    bits256 txid; uint64_t paytoshis; cJSON *retjson; char numstr[32],*coin,*dest,*hexstr; int32_t retval = 0;
    if ( mp->bobflag == 0 )
    {
        coin = mp->rel.name;
        paytoshis = mp->rel.satoshis;
        if ( hushdex_zonly(&mp->rel) != 0 )
            dest = mp->bob.recvZaddr;
        else dest = mp->bob.recvaddr;
        sprintf(numstr,"%llu",(long long)paytoshis);
        jaddstr(payment,"alicepays",numstr);
        jaddstr(payment,"bobdestaddr",dest);
        txid = hushdex_coinpayment(mp->origid,mp->OTCmode,&mp->rel,dest,paytoshis,mp->approval,mp->bob.secp,senderpub);
        jaddbits256(payment,"alicepayment",txid);
        mp->alicepayment = txid;
        hexstr = 0; // get it from rawtransaction of txid
        jaddstr(payment,"alicetx",hexstr);
    }
    else
    {
        coin = mp->base.name;
        paytoshis = mp->base.satoshis;
        if ( hushdex_zonly(&mp->base) != 0 )
            dest = mp->alice.recvZaddr;
        else dest = mp->alice.recvaddr;
        sprintf(numstr,"%llu",(long long)paytoshis);
        jaddstr(payment,"bobpays",numstr);
        jaddstr(payment,"alicedestaddr",dest);
        txid = hushdex_coinpayment(mp->origid,mp->OTCmode,&mp->base,dest,paytoshis,mp->approval,mp->alice.secp,senderpub);
        jaddbits256(payment,"bobpayment",txid);
        mp->bobpayment = txid;
        hexstr = 0; // get it from rawtransaction of txid
        jaddstr(payment,"bobtx",hexstr);
    }
    hexstr = hushdex_submit(payment,!mp->bobflag);
    if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"payment",senderpub,"","")) != 0 )
    {
        if ( (mp->paymentids[0]= juint(retjson,"id")) != 0 )
            retval = 1;
        printf("%u: %.8f %s -> %s, paymentid[0] %u\n",mp->origid,dstr(paytoshis),coin,dest,mp->paymentids[0]);
        hushdex_status(mp,SUBATOMIC_PAYMENT);
        free_json(retjson);
    }
    free(hexstr);
    return(retval);
}

int32_t hushdex_paidinfull(struct msginfo *mp,cJSON *paid,cJSON *msgjson,char *senderpub)
{
    char *hexstr; cJSON *retjson; int32_t retval = 0;
    jaddstr(paid,"paid","in full");
    hushdex_extrafields(paid,msgjson);
    hexstr = hushdex_submit(paid,!mp->bobflag);
    if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"paid",senderpub,"","")) != 0 )
    {
        if ( (mp->paidid= juint(retjson,"id")) != 0 )
            retval = 1;
        printf("%u paidid.%u\n",mp->origid,mp->paidid);
        hushdex_status(mp,SUBATOMIC_PAIDINFULL);
        free_json(retjson);
    }
    free(hexstr);
    return(retval);
}

int32_t hushdex_closed(struct msginfo *mp,cJSON *closed,cJSON *msgjson,char *senderpub)
{
    char *hexstr; cJSON *retjson; int32_t retval = 0;
    jaddnum(closed,"closed",mp->origid);
    hushdex_extrafields(closed,msgjson);
    hexstr = hushdex_submit(closed,!mp->bobflag);
    if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"closed",senderpub,"","")) != 0 )
    {
        if ( (mp->closedid= juint(retjson,"id")) != 0 )
            retval = 1;
        hushdex_status(mp,SUBATOMIC_CLOSED);
        printf("%u closedid.%u\n",mp->origid,mp->closedid);
        free_json(retjson);
    }
    free(hexstr);
    return(retval);
}

uint32_t hushdex_alice_openrequest(struct msginfo *origmp)
{
    struct msginfo *mp; cJSON *retjson,*openrequest; char *hexstr,*str,tmpstr[32];
    mp = hushdex_tracker(origmp->origid);
    mp->origid = origmp->origid;
    mp->rel.satoshis = origmp->rel.satoshis;
    mp->rel.istoken = origmp->rel.istoken;
    strcpy(mp->rel.tokenid,origmp->rel.tokenid);
    strcpy(mp->rel.name,origmp->rel.name);
    strcpy(mp->rel.coin,hushdex_checkname(tmpstr,mp,1,origmp->rel.name));
    strcpy(mp->alice.pubkey,DPOW_pubkeystr);
    strcpy(mp->alice.secp,DPOW_secpkeystr);
    strcpy(mp->alice.recvZaddr,DPOW_recvZaddr);
    strcpy(mp->alice.recvaddr,DPOW_recvaddr);
    printf("rel.%s/%s %s openrequest %u status.%d (%s/%s)\n",mp->rel.name,mp->rel.coin,mp->rel.tokenid,mp->origid,mp->status,mp->alice.recvaddr,mp->alice.recvZaddr);
    if ( mp->status == 0 && hushdex_orderbook_mpset(mp,"") != 0 )
    {
        strcpy(mp->bob.pubkey,mp->senderpub);
        if ( hushdex_zonly(&mp->base) != 0 || hushdex_zonly(&mp->rel) != 0 )
            mp->OTCmode = 1;
        else mp->OTCmode = SUBATOMIC_OTCDEFAULT;
        strcpy(origmp->base.name,mp->base.name);
        strcpy(origmp->base.coin,mp->base.coin);
        origmp->base.istoken = mp->base.istoken;
        strcpy(origmp->base.tokenid,mp->base.tokenid);
        origmp->OTCmode = mp->OTCmode;
        if ( mp->rel.istoken != 0 && ((mp->rel.satoshis % SATOSHIDEN) != 0 || mp->rel.iszaddr != 0) )
        {
            printf("%u cant do zaddr or fractional rel %s.%s tokens %.8f\n",mp->origid,mp->rel.coin,mp->rel.tokenid,dstr(mp->rel.satoshis));
            return(0);
        }
        else if ( mp->base.istoken != 0 && ((mp->base.satoshis % SATOSHIDEN) != 0 || mp->base.iszaddr != 0 ) )
        {
            printf("%u cant do zaddr or fractional base %s.%s tokens %.8f\n",mp->origid,mp->base.coin,mp->base.tokenid,dstr(mp->base.satoshis));
            return(0);
        }
        else if ( (openrequest= hushdex_mpjson(mp)) != 0 )
        {
            hexstr = hushdex_submit(openrequest,!mp->bobflag);
            if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"openrequest",mp->bob.pubkey,"","")) != 0 )
            {
                mp->openrequestid = juint(retjson,"id");
                printf("%u openrequest.%u -> (%s)\n",mp->origid,mp->openrequestid,mp->bob.pubkey);
                hushdex_status(mp,SUBATOMIC_OPENREQUEST);
                free_json(retjson);
            }
            free(hexstr);
        }
    }
    return(mp->openrequestid);
}

void hushdex_bob_gotopenrequest(uint32_t inboxid,char *senderpub,cJSON *msgjson,char *basename,char *relname)
{
    struct msginfo *mp; cJSON *approval; int32_t origid; char *addr,tmpstr[32],*coin,*acname="";
    origid = juint(msgjson,"origid");
    mp = hushdex_tracker(origid);
    strcpy(mp->base.name,basename);
    strcpy(mp->base.coin,hushdex_checkname(tmpstr,mp,0,basename));
    strcpy(mp->rel.name,relname);
    strcpy(mp->rel.coin,hushdex_checkname(tmpstr,mp,1,relname));
    mp->origid = origid;
    mp->rel.satoshis = j64bits(msgjson,"relsatoshis");
    mp->bobflag = 1;
    strcpy(mp->bob.pubkey,DPOW_pubkeystr);
    strcpy(mp->bob.secp,DPOW_secpkeystr);
    strcpy(mp->bob.recvZaddr,DPOW_recvZaddr);
    strcpy(mp->bob.recvaddr,DPOW_recvaddr);
    if ( (addr= jstr(msgjson,"aliceaddr")) != 0 )
        strcpy(mp->alice.recvaddr,addr);
    if ( (addr= jstr(msgjson,"aliceZaddr")) != 0 )
        strcpy(mp->alice.recvZaddr,addr);
    if ( (addr= jstr(msgjson,"alicesecp")) != 0 )
        strcpy(mp->alice.secp,addr);
    if ( hushdex_zonly(&mp->base) != 0 || hushdex_zonly(&mp->rel) != 0 )
        mp->OTCmode = 1;
    else mp->OTCmode = SUBATOMIC_OTCDEFAULT;
    printf("%u got open request\n",mp->origid);
    if ( mp->status == 0 && hushdex_orderbook_mpset(mp,basename) != 0 && (approval= hushdex_mpjson(mp)) != 0 )
    {
        if ( mp->rel.istoken != 0 && ((mp->rel.satoshis % SATOSHIDEN) != 0 || mp->rel.iszaddr != 0) )
        {
            printf("%u cant do zaddr or fractional rel %s.%s tokens %.8f\n",mp->origid,mp->rel.coin,mp->rel.tokenid,dstr(mp->rel.satoshis));
            hushdex_closed(mp,approval,msgjson,senderpub);
            return;
        }
        else if ( mp->base.istoken != 0 && ((mp->base.satoshis % SATOSHIDEN) != 0 || mp->base.iszaddr != 0 ) )
        {
            printf("%u cant do zaddr or fractional base %s.%s tokens %.8f\n",mp->origid,mp->base.coin,mp->base.tokenid,dstr(mp->base.satoshis));
            hushdex_closed(mp,approval,msgjson,senderpub);
            return;
        }
        else if ( hushdex_getbalance(&mp->base) < mp->base.satoshis )
        {
            printf("%u bob node low on %s funds! %.8f not enough for %.8f\n",mp->origid,mp->base.coin,dstr(hushdex_getbalance(&mp->base)),dstr(mp->base.satoshis));
            hushdex_closed(mp,approval,msgjson,senderpub);
        }
        else
        {
            printf("%u bob (%s/%s) gotopenrequest origid.%u status.%d (%s/%s) SENDERPUB.(%s)\n",mp->origid,mp->base.name,mp->rel.name,mp->origid,mp->status,mp->bob.recvaddr,mp->bob.recvZaddr,senderpub);
            hushdex_approved(mp,approval,msgjson,senderpub);
        }
    }
}

int32_t hushdex_channelapproved(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp)
{
    struct msginfo *mp; cJSON *approval; char *addr,*coin,*acname; int32_t retval = 0;
    mp = hushdex_tracker(juint(msgjson,"origid"));
    if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (approval= hushdex_mpjson(mp)) != 0 )
    {
        printf("%u iambob.%d (%s/%s) channelapproved origid.%u status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->origid,mp->status);
        if ( mp->bobflag == 0 && mp->status == SUBATOMIC_OPENREQUEST )
        {
            if ( (addr= jstr(msgjson,"bobaddr")) != 0 )
                strcpy(mp->bob.recvaddr,addr);
            if ( (addr= jstr(msgjson,"bobZaddr")) != 0 )
                strcpy(mp->bob.recvZaddr,addr);
            if ( (addr= jstr(msgjson,"bobsecp")) != 0 )
                strcpy(mp->bob.secp,addr);
            retval = hushdex_approved(mp,approval,msgjson,senderpub);
        }
        else if ( mp->bobflag != 0 && mp->status == SUBATOMIC_APPROVED )
            retval = hushdex_opened(mp,approval,msgjson,senderpub);
    }
    return(retval);
}

int32_t hushdex_incomingopened(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp)
{
    struct msginfo *mp; cJSON *payment; int32_t retval = 0;
    mp = hushdex_tracker(juint(msgjson,"origid"));
    if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (payment= hushdex_mpjson(mp)) != 0 )
    {
        printf("%u iambob.%d (%s/%s) incomingchannel status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->status);
        if ( mp->bobflag == 0 && mp->status == SUBATOMIC_APPROVED )
            retval = hushdex_payment(mp,payment,msgjson,senderpub);
        else if ( mp->bobflag != 0 && mp->status == SUBATOMIC_OPENED )
            retval = 1; // nothing to do
    }
    return(retval);
}

int32_t hushdex_incomingpayment(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp)
{
    static FILE *fp;
    struct msginfo *mp; cJSON *pay,*rawtx,*retjson; bits256 txid; char str[65],*hexstr; int32_t retval = 0;
    mp = hushdex_tracker(juint(msgjson,"origid"));
    if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (pay= hushdex_mpjson(mp)) != 0 )
    {
        printf("%u iambob.%d (%s/%s) incomingpayment status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->status);
        if ( mp->bobflag == 0 )
        {
            txid = jbits256(msgjson,"bobpayment");
            jaddbits256(msgjson,"alicepayment",mp->alicepayment);
            printf("%u alice waits for %s.%s to be in mempool (%.8f -> %s)\n",mp->origid,mp->base.name,bits256_str(str,txid),dstr(mp->base.satoshis),hushdex_zonly(&mp->base) == 0 ? mp->alice.recvaddr : mp->alice.recvZaddr);
            hexstr = jstr(msgjson,"bobtx");
            if ( (rawtx= hushdex_txidwait(&mp->base,txid,hexstr,SUBATOMIC_TIMEOUT,senderpub)) != 0 )
            {
                if ( hushdex_verifypayment(&mp->base,rawtx,mp->base.satoshis,hushdex_zonly(&mp->base) == 0 ? mp->alice.recvaddr : mp->alice.recvZaddr,txid) >= 0 )
                    mp->gotpayment = 1;
                free_json(rawtx);
            }
            if ( mp->gotpayment != 0 )
            {
                printf("%u SWAP COMPLETE <<<<<<<<<<<<<<<<\n",mp->origid);
                SUBATOMIC_retval = 0;
                if ( mp->base.iszaddr == 0 )
                {
                    sprintf(str,"%u",mp->origid);
                    if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,bits256_str(str,mp->alicepayment),(char *)"completed",str,DPOW_pubkeystr,"","")) != 0 )
                        free_json(retjson);
                }
            }
            else
            {
                printf("%u SWAP INCOMPLETE, waiting on %s.%s\n",mp->origid,mp->base.name,bits256_str(str,txid));
                if ( (fp= fopen("SUBATOMIC.incomplete","a+")) != 0 )
                {
                    char *jsonstr = jprint(msgjson,0);
                    fwrite(jsonstr,1,strlen(jsonstr),fp);
                    fputc('\n',fp);
                    fclose(fp);
                    free(jsonstr);
                }
                if ( mp->base.iszaddr == 0 )
                {
                    sprintf(str,"%u",mp->origid);
                    if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,bits256_str(str,mp->alicepayment),(char *)"incomplete",str,DPOW_pubkeystr,"","")) != 0 )
                        free_json(retjson);
                }
                hushdex_closed(mp,pay,msgjson,senderpub);
                exit(-1);
            }
        }
        if ( mp->gotpayment != 0 )
            retval = hushdex_paidinfull(mp,pay,msgjson,senderpub);
        else
        {
            if ( mp->bobflag != 0 && mp->status == SUBATOMIC_OPENED )
            {
                txid = jbits256(msgjson,"alicepayment");
                printf("%u bob waits for %s.%s to be in mempool (%.8f -> %s)\n",mp->origid,mp->rel.name,bits256_str(str,txid),dstr(mp->rel.satoshis),hushdex_zonly(&mp->rel) == 0 ? mp->bob.recvaddr : mp->bob.recvZaddr);
                hexstr = jstr(msgjson,"alicetx");
                if ( (rawtx= hushdex_txidwait(&mp->rel,txid,hexstr,SUBATOMIC_TIMEOUT,senderpub)) != 0 )
                {
                    if ( hushdex_verifypayment(&mp->rel,rawtx,mp->rel.satoshis,hushdex_zonly(&mp->rel) == 0 ? mp->bob.recvaddr : mp->bob.recvZaddr,txid) >= 0 )
                        mp->gotpayment = 1;
                    free_json(rawtx);
                }
                if ( mp->gotpayment != 0 )
                {
                    retval = hushdex_payment(mp,pay,msgjson,senderpub);
                    jaddbits256(msgjson,"bobpayment",mp->bobpayment);
                    if ( mp->rel.iszaddr == 0 )
                    {
                        sprintf(str,"%u",mp->origid);
                        if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,bits256_str(str,mp->bobpayment),(char *)"completed",str,DPOW_pubkeystr,"","")) != 0 )
                            free_json(retjson);
                    }
                    printf("%u SWAP COMPLETE <<<<<<<<<<<<<<<<\n",mp->origid);
                    if ( (fp= fopen("SUBATOMIC.proof","rb+")) == 0 )
                        fp = fopen("SUBATOMIC.proof","wb");
                    if ( fp != 0 )
                    {
                        char *jsonstr = jprint(msgjson,0);
                        fseek(fp,0,SEEK_END);
                        fwrite(jsonstr,1,strlen(jsonstr),fp);
                        fputc('\n',fp);
                        fflush(fp);
                        free(jsonstr);
                    }
                } else printf("%u SWAP INCOMPLETE: %s\n",mp->origid,jprint(msgjson,0));
            }
        }
    }
    return(retval);
}

int32_t hushdex_incomingfullypaid(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp)
{
    struct msginfo *mp; cJSON *closed; int32_t retval = 0;
    mp = hushdex_tracker(juint(msgjson,"origid"));
    if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (closed= hushdex_mpjson(mp)) != 0 )
    {
        printf("%u iambob.%d (%s/%s) incomingfullypaid status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->status);
        // error check msgjson vs M
        if ( mp->bobflag == 0 && mp->status == SUBATOMIC_PAIDINFULL )
            retval = hushdex_closed(mp,closed,msgjson,senderpub);
        else if ( mp->bobflag != 0 && mp->status == SUBATOMIC_PAYMENT )
            retval = hushdex_paidinfull(mp,closed,msgjson,senderpub);
    }
    return(retval);
}

int32_t hushdex_incomingclosed(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp)
{
    struct msginfo *mp; cJSON *closed; int32_t retval = 0;
    mp = hushdex_tracker(juint(msgjson,"origid"));
    if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (closed= hushdex_mpjson(mp)) != 0 )
    {
        printf("%u iambob.%d (%s/%s) incomingclose status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->status);
        if ( mp->bobflag != 0 )
            dpow_cancel(mp->origid);
        if ( mp->status < SUBATOMIC_CLOSED )
        {
            retval = hushdex_closed(mp,closed,msgjson,senderpub);
            hushdex_status(mp,SUBATOMIC_CLOSED);
        }
        retval = 1;
    }
    return(retval);
}

int32_t hushdex_ismine(int32_t bobflag,cJSON *json,char *basename,char *relname)
{
    char *base,*rel;
    if ( (base= jstr(json,"base")) != 0 && (rel= jstr(json,"rel")) != 0 )
    {
        if ( strcmp(base,basename) == 0 && strcmp(rel,relname) == 0 )
            return(1);
        if ( bobflag != 0 )
        {
            if ( strcmp(basename,"#allfiles") == 0 && base[0] == '#' )
                return(1);
            fprintf(stderr,"skip ismine (%s/%s) vs (%s/%s)\n",basename,relname,base,rel);
        }
    }
    return(0);
}

void hushdex_tokensregister(int32_t priority)
{
    char *token_name,*tokenid,existing[65]; cJSON *tokens,*token; int32_t i,numtokens;
    if ( SUBATOMIC_json != 0 && (tokens= jarray(&numtokens,SUBATOMIC_json,"tokens")) != 0 )
    {
        for (i=0; i<numtokens; i++)
        {
            token = jitem(tokens,i);
            if ( token != 0 )
            {
                token_name = jfieldname(token);
                tokenid = jstr(token,token_name);
                //fprintf(stderr,"TOKEN (%s %s)\n",token_name,tokenid);
                if ( token_name != 0 && tokenid != 0 )
                    dpow_tokenregister(existing,priority,token_name,tokenid);
            }
        }
    }
}

void hushdex_filesregister(int32_t priority)
{
    char *fname,*tokenid,*coin,existing[512]; int64_t price; cJSON *files,*file,*prices; int32_t i,j,m,numfiles;
    if ( SUBATOMIC_json != 0 && (files= jarray(&numfiles,SUBATOMIC_json,"files")) != 0 )
    {
        for (i=0; i<numfiles; i++)
        {
            file = jitem(files,i);
            if ( file != 0 )
            {
                // {"filename":"hushd",prices:[{"HUSH":1}, {"ZEC:1"}]}
                fname = jstr(file,"filename");
                if ( (prices= jarray(&m,file,"prices")) != 0 && m > 0 )
                {
                    for (j=0; j<m; j++)
                    {
                        coin = jfieldname(jitem(prices,j));
                        price = (jdouble(jitem(prices,j),coin)*SATOSHIDEN + 0.00000000499999);
                        //pricestr = jstr(jitem(prices,j),coin);
                        //fprintf(stderr,"%s %.8f, ",coin,dstr(price));
                        dpow_fileregister(existing,priority,fname,coin,price);
                    }
                }
            }
        }
    }
}

void hushdex_loop(struct msginfo *mp)
{
    static char *tagBs[] = { "openrequest", "approved", "opened", "payment", "paid", "closed" };
    static uint32_t stopats[sizeof(tagBs)/sizeof(*tagBs)];
    struct inboxinfo **ptrs,*ptr; char *tagB; int32_t i,iter,n,msgs=0,mask=0; cJSON *inboxjson;
    fprintf(stderr,"start hushdex_loop iambob.%d %s -> %s, %u %llu %u\n",mp->bobflag,mp->base.name,mp->rel.name,mp->origid,(long long)mp->rel.satoshis,mp->openrequestid);
    while ( 1 )
    {
        if ( msgs == 0 )
        {
            sleep(1);
            fflush(stdout);
            if ( mp->bobflag != 0 )
            {
                dpow_pubkeyregister(SUBATOMIC_PRIORITY);
                hushdex_tokensregister(SUBATOMIC_PRIORITY);
                hushdex_filesregister(SUBATOMIC_PRIORITY);
            }
        }
        msgs = 0;
        for (iter=0; iter<(int32_t)(sizeof(tagBs)/sizeof(*tagBs)); iter++)
        {
            tagB = tagBs[iter];
            if ( (ptrs= dpow_inboxcheck(&n,&stopats[iter],tagB)) != 0 )
            {
                for (i=0; i<n; i++)
                {
                    msgs++;
                    if ( (ptr= ptrs[i]) != 0 )
                    {
                        if ( (inboxjson= cJSON_Parse(ptr->jsonstr)) != 0 )
                        {
                            if ( jint(inboxjson,"tobob") != mp->bobflag )
                                continue;
                            if ( hushdex_ismine(mp->bobflag,inboxjson,mp->base.name,mp->rel.name) != 0 )
                            {
                                if ( strcmp(tagB,"openrequest") == 0 && mp->bobflag != 0 )
                                    hushdex_bob_gotopenrequest(ptr->shorthash,ptr->senderpub,inboxjson,mp->base.name,mp->rel.name);
                                else if ( strcmp(tagB,"approved") == 0 )
                                    mask |= hushdex_channelapproved(ptr->shorthash,ptr->senderpub,inboxjson,mp) << 0;
                                else if ( strcmp(tagB,"opened") == 0 )
                                    mask |= hushdex_incomingopened(ptr->shorthash,ptr->senderpub,inboxjson,mp) << 1;
                                else if ( strcmp(tagB,"payment") == 0 )
                                    mask |= hushdex_incomingpayment(ptr->shorthash,ptr->senderpub,inboxjson,mp) << 2;
                                else if ( strcmp(tagB,"paid") == 0 )
                                    mask |= hushdex_incomingfullypaid(ptr->shorthash,ptr->senderpub,inboxjson,mp) << 3;
                                else if ( strcmp(tagB,"closed") == 0 )
                                    mask |= hushdex_incomingclosed(ptr->shorthash,ptr->senderpub,inboxjson,mp) * 0x1f;
                                else fprintf(stderr,"iambob.%d unknown unexpected tagB.(%s)\n",mp->bobflag,tagB);
                            }
                            free_json(inboxjson);
                        } else fprintf(stderr,"hushdex iambob.%d loop got unparseable(%s)\n",mp->bobflag,ptr->jsonstr);
                        free(ptr);
                        ptrs[i] = 0;
                    }
                }
                free(ptrs);
            }
        }
        if ( mp->bobflag == 0 && (mask & 0x1f) == 0x1f )
        {
            printf("alice %u %llu %u finished\n",mp->origid,(long long)mp->rel.satoshis,mp->openrequestid);
            break;
        }
    }
}

int32_t main(int32_t argc,char **argv)
{
    char *fname = "hushdex.json";
    int32_t i,height; char *coin,*kcli,*hushdex,*hashstr,*acname=(char *)""; cJSON *retjson; bits256 blockhash; char checkstr[65],str[65],str2[65],tmpstr[32]; long fsize; struct msginfo M;
    memset(&M,0,sizeof(M));
    srand((int32_t)time(NULL));
    if ( (hushdex= filestr(&fsize,fname)) == 0 )
    {
        fprintf(stderr,"cant load %s file\n",fname);
        exit(-1);
    }
    if ( (SUBATOMIC_json= cJSON_Parse(hushdex)) == 0 )
    {
        fprintf(stderr,"cant parse hushdex.json file (%s)\n",hushdex);
        exit(-1);
    }
    free(hushdex);
    if ( argc >= 4 )
    {
        if ( dpow_pubkey() < 0 )
        {
            fprintf(stderr,"couldnt set pubkey for ZEX\n");
            return(-1);
        }
        coin = (char *)argv[1];
        if ( argv[2][0] != 0 ) {
            REFCOIN_CLI = (char *)argv[2];
        } else {
            acname = coin;
        }
        hashstr = (char *)argv[3];
        strcpy(M.rel.coin,hushdex_checkname(tmpstr,&M,1,coin));
        strcpy(M.rel.name,coin);
        if ( argc == 4 && strlen(hashstr) == 64 ) // for blocknotify usage, seems not needed
        {
            height = get_coinheight(coin,acname,&blockhash);
            bits256_str(checkstr,blockhash);
            if ( strcmp(checkstr,hashstr) == 0 )
            {
                fprintf(stderr,"%s: (%s) %s height.%d\n",coin,REFCOIN_CLI!=0?REFCOIN_CLI:"",checkstr,height);
                if ( (retjson= dpow_ntzdata(coin,SUBATOMIC_PRIORITY,height,blockhash)) != 0 )
                    free_json(retjson);
            } else fprintf(stderr,"coin.%s (%s) %s vs %s, height.%d\n",coin,REFCOIN_CLI!=0?REFCOIN_CLI:"",checkstr,hashstr,height);
            if ( strcmp("BTC",coin) != 0 )
            {
                bits256 prevntzhash,ntzhash; int32_t prevntzheight,ntzheight; uint32_t ntztime,prevntztime; char hexstr[81]; cJSON *retjson2;
                prevntzhash = dpow_ntzhash(coin,&prevntzheight,&prevntztime);
                if ( (retjson= get_getinfo(coin,acname)) != 0 )
                {
                    ntzheight = juint(retjson,"notarized");
                    ntzhash = jbits256(retjson,"notarizedhash");
                    if ( ntzheight > prevntzheight )
                    {
                        get_coinmerkleroot(coin,acname,ntzhash,&ntztime);
                        fprintf(stderr,"NOTARIZATION %s.%d %s t.%u\n",coin,ntzheight,bits256_str(str,ntzhash),ntztime);
                        bits256_str(hexstr,ntzhash);
                        sprintf(&hexstr[64],"%08x",ntzheight);
                        sprintf(&hexstr[72],"%08x",ntztime);
                        hexstr[80] = 0;
                        if ( (retjson2= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,coin,"notarizations",DPOW_pubkeystr,"","")) != 0 )
                            free_json(retjson2);
                    }
                    else if ( ntzheight == prevntzheight && memcmp(&prevntzhash,&ntzhash,32) != 0 )
                        fprintf(stderr,"NTZ ERROR %s.%d %s != %s\n",coin,ntzheight,bits256_str(str,ntzhash),bits256_str(str2,prevntzhash));
                    free_json(retjson);
                }
            }
        }
        else if ( argc == 5 && atol(hashstr) > 10000 )
        {
            char checkstr[32]; uint64_t mult = 1;
            M.origid = (uint32_t)atol(hashstr);
            sprintf(checkstr,"%u",M.origid);
            if ( strcmp(checkstr,hashstr) == 0 ) // alice
            {
                M.rel.satoshis = (uint64_t)(atof(argv[4])*SATOSHIDEN+0.0000000049999);
                for (i=0; M.rel.name[i]!=0; i++)
                    if ( M.rel.name[i] == '.' )
                    {
                        mult = SATOSHIDEN;
                        break;
                    }
                if ( hushdex_getbalance(&M.rel) < M.rel.satoshis/mult )
                {
                    fprintf(stderr,"not enough balance %s %.8f for %.8f\n",M.rel.coin,dstr(hushdex_getbalance(&M.rel)),dstr(M.rel.satoshis/mult));
                    return(-1);
                }
                fprintf(stderr,"hushdex_channel_alice (%s/%s) %s %u with %.8f %llu\n",M.rel.name,M.rel.coin,hashstr,M.origid,atof(argv[4]),(long long)M.rel.satoshis);
                dpow_pubkeyregister(SUBATOMIC_PRIORITY);
                M.openrequestid = hushdex_alice_openrequest(&M);
                if ( M.openrequestid != 0 )
                    hushdex_loop(&M);
            } else fprintf(stderr,"checkstr mismatch %s %s != %s\n",coin,hashstr,checkstr);
        }
        else
        {
            M.bobflag = 1;
            strcpy(M.base.name,hashstr);
            strcpy(M.base.coin,hushdex_checkname(tmpstr,&M,0,hashstr));
            hushdex_loop(&M); // while ( 1 ) loop for each relcoin -> basecoin
        }
    }
    return(SUBATOMIC_retval);
}

