UNPKG

tonweb

Version:

TonWeb - JavaScript API for TON blockchain

488 lines (438 loc) 22.3 kB
const {Contract} = require("../index"); const {Cell} = require("../../boc"); const {nacl, hexToBytes, BN} = require("../../utils"); const {parseAddress} = require("../token/nft/NftUtils"); const { writePublicKey, writeSignature, createSignatureCell, tag_init, tag_cooperative_close, tag_cooperative_commit, tag_start_uncooperative_close, tag_challenge_state, tag_settle_conditionals, tag_state, op_top_up_balance, op_init_channel, op_cooperative_close, op_cooperative_commit, op_start_uncooperative_close, op_challenge_quarantined_state, op_settle_conditionals, op_finish_uncooperative_close, op_channel_closed, createTopUpBalance, createInitChannelBody, createCooperativeCloseChannelBody, createCooperativeCommitBody, createConditionalPayment, createSemiChannelBody, createSemiChannelState, createSignedSemiChannelState, createStartUncooperativeCloseBody, createChallengeQuarantinedStateBody, createSettleConditionalsBody, createFinishUncooperativeClose, createOneSignature, createTwoSignature } = require("./PaymentUtils"); const PAYMENT_CHANNEL_CODE_HEX = 'B5EE9C72410230010007FB000114FF00F4A413F4BCF2C80B0102012002030201480405000AF26C21F0190202CB06070201202E2F020120080902012016170201200A0B0201200C0D0009D3610F80CC001D6B5007434C7FE8034C7CC1BC0FE19E0201580E0F0201201011002D3E11DBC4BE11DBC43232C7FE11DBC47E80B2C7F2407320008B083E1B7B51343480007E187E80007E18BE80007E18F4FFC07E1934FFC07E1974DFC07E19BC01887080A7F4C7C07E1A34C7C07E1A7D01007E1AB7807080E535007E1AF7BE1B2002012012130201201415008D3E13723E11BE117E113E10540132803E10BE80BE10FE8084F2FFC4B2FFF2DFFC02887080A7FE12BE127E121400F2C7C4B2C7FD0037807080E53E12C073253E1333C5B8B27B5520004D1C3C02FE106CFCB8193E803E800C3E1096283E18BE10C0683E18FE10BE10E8006EFCB819BC032000CF1D3C02FE106CFCB819348020C235C6083E4040E4BE1124BE117890CC3E443CB81974C7C060841A5B9A5D2EBCB81A3E118074DFD66EBCB81CBE803E800C3E1094882FBE10D4882FAC3CB819807E18BE18FE12F43E800C3E10BE10E80068006E7CB8199FFE187C0320004120843777222E9C20043232C15401B3C594013E808532DA84B2C7F2DFF2407EC02002012018190201D42B2C0201201A1B0201201E1F0201201C1D00E5473F00BD401D001D401D021F90102D31F01821043436D74BAF2E068F84601D37F59BAF2E072F844544355F910F8454330F910B0F2E065D33FD33F30F84822B9F84922B9B0F2E06C21F86820F869F84A6E915B8E19F84AD0D33FFA003171D721D33F305033BC02BCB1936DF86ADEE2F800F00C8006F3E12F43E800C7E903E900C3E09DBC41CBE10D62F24CC20C1B7BE10FE11963C03FE10BE11A04020BC03DC3E185C3E189C3E18DB7E1ABC032000B51D3C02F5007400750074087E4040B4C7C0608410DB1BDCEEBCB81A3E118074DFD66EBCB81CBE111510D57E443E1150CC3E442C3CB8197E80007E18BE80007E18F4CFF4CFCC3E1208AE7E1248AE6C3CB81B007E1A3E1A7E003C042001C1573F00BF84A6EF2E06AD2008308D71820F9012392F84492F845E24130F910F2E065D31F018210556E436CBAF2E068F84601D37F59BAF2E072D401D08308D71820F901F8444130F910F2E06501D430D08308D71820F901F8454130F910F2E06501820020120222301FED31F01821043685374BAF2E068F84601D37F59BAF2E072D33FFA00F404552003D200019AD401D0D33FFA00F40430937F206DE2303205D31F01821043685374BAF2E068F84601D37F59BAF2E072D33FFA00F404552003D200019AD401D0D33FFA00F40430937F206DE23032F8485280BEF8495250BEB0524BBE1AB0527ABE19210064B05215BE14B05248BE17B0F2E06970F82305C8CB3F5004FA0215F40015CB3F5004FA0212F400CB1F12CA00CA00C9F86AF00C01C31CFC02FE129BACFCB81AF48020C235C6083E4048E4BE1124BE1178904C3E443CB81974C7C0608410DA19D46EBCB81A3E118074DFD66EBCB81CB5007420C235C6083E407E11104C3E443CB81940750C3420C235C6083E407E11504C3E443CB81940602403F71CFC02FE129BACFCB81AF48020C235C6083E4048E4BE1124BE1178904C3E443CB81974C7C0608410DB10DBAEBCB81A3E118074DFD66EBCB81CBD010C3E12B434CFFE803D0134CFFE803D0134C7FE11DBC4148828083E08EE7CB81BBE11DBC4A83E08EF3CB81C34800C151D5A64D6D4C8F7A2B98E82A49B08B8C3816028292A01FCD31F01821043685374BAF2E068F84601D37F59BAF2E072D33FFA00F404552003D200019AD401D0D33FFA00F40430937F206DE2303205D31F01821043685374BAF2E068F84601D37F59BAF2E072D33FFA00F404552003D200019AD401D0D33FFA00F40430937F206DE230325339BE5381BEB0F8495250BEB0F8485290BEB02502FE5237BE16B05262BEB0F2E06927C20097F84918BEF2E0699137E222C20097F84813BEF2E0699132E2F84AD0D33FFA00F404D33FFA00F404D31FF8476F105220A0F823BCF2E06FD200D20030B3F2E073209C3537373A5274BC5263BC12B18E11323939395250BC5299BC18B14650134440E25319BAB3F2E06D9130E30D7F05C82627002496F8476F1114A098F8476F1117A00603E203003ECB3F5004FA0215F40012CB3F5004FA0213F400CB1F12CA00CA00C9F86AF00C00620A8020F4966FA5208E213050038020F4666FA1208E1001FA00ED1E15DA119450C3A00B9133E2923430E202926C21E2B31B000C3535075063140038C8CB3F5004FA0212F400CB3F5003FA0213F400CB1FCA00C9F86AF00C00D51D3C02FE129BACFCB81AFE12B434CFFE803D010C74CFFE803D010C74C7CC3E11DBC4283E11DBC4A83E08EE7CB81C7E003E10886808E87E18BE10D400E816287E18FE10F04026BE10BE10E83E189C3E18F7BE10B04026BE10FE10A83E18DC3E18F780693E1A293E1A7C042001F53B7EF4C7C8608419F1F4A06EA4CC7C037808608403818830AEA54C7C03B6CC780C882084155DD61FAEA54C3C0476CC780820841E6849BBEEA54C3C04B6CC7808208407C546B3EEA54C3C0576CC780820840223AA8CAEA54C3C05B6CC7808208419BDBC1A6EA54C3C05F6CC780C60840950CAA46EA53C0636CC78202D0008840FF2F00075BC7FE3A7805FC25E87D007D207D20184100D0CAF6A1EC7C217C21B7817C227C22B7817C237C23FC247C24B7817C2524C3B7818823881B22A021984008DBD0CABA7805FC20C8B870FC253748B8F07C256840206B90FD0018C020EB90FD0018B8EB90E98F987C23B7882908507C11DE491839707C23B788507C23B789507C11DE48B9F03A4331C4966'; class PaymentChannel extends Contract { /** * @param provider {HttpProvider} * @param options {{isA: boolean, channelId: BN, myKeyPair: nacl.SignKeyPair, hisPublicKey: Uint8Array, initBalanceA: BN, initBalanceB: BN, addressA: Address, addressB: Address, closingConfig?: {quarantineDuration: number, misbehaviorFine: BN, conditionalCloseDuration: number}, excessFee?: BN}} */ constructor(provider, options) { options.publicKeyA = options.isA ? options.myKeyPair.publicKey : options.hisPublicKey; options.publicKeyB = !options.isA ? options.myKeyPair.publicKey : options.hisPublicKey; options.wc = options.wc || 0; options.code = options.code || Cell.oneFromBoc(PAYMENT_CHANNEL_CODE_HEX); super(provider, options); } /** * @override * @private * @return {Cell} cell contains payment channel data */ createDataCell() { const cell = new Cell(); cell.bits.writeBit(0); // inited cell.bits.writeCoins(0); // balance_A cell.bits.writeCoins(0); // balance_B writePublicKey(cell, this.options.publicKeyA); // key_A writePublicKey(cell, this.options.publicKeyB); // key_B cell.bits.writeUint(this.options.channelId, 128); // channel_id const closingConfig = new Cell(); closingConfig.bits.writeUint(this.options.closingConfig?.quarantineDuration || 0, 32); // quarantin_duration closingConfig.bits.writeCoins(this.options.closingConfig?.misbehaviorFine || new BN(0)); // misbehavior_fine closingConfig.bits.writeUint(this.options.closingConfig?.conditionalCloseDuration || 0, 32); // conditional_close_duration cell.refs[0] = closingConfig; cell.bits.writeUint(0, 32); // commited_seqno_A cell.bits.writeUint(0, 32); // commited_seqno_B cell.bits.writeBit(false); // quarantin ref const paymentConfig = new Cell(); paymentConfig.bits.writeCoins(this.options.excessFee || new BN(0)); // excess_fee paymentConfig.bits.writeAddress(this.options.addressA); // addr_A paymentConfig.bits.writeAddress(this.options.addressB); // addr_B cell.refs[1] = paymentConfig; return cell; } /** * @private * @param op {number} * @param cellForSigning {Cell} * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createOneSignature(op, cellForSigning) { const signature = nacl.sign.detached(await cellForSigning.hash(), this.options.myKeyPair.secretKey); const cell = createOneSignature({ op, isA: this.options.isA, signature, cell: cellForSigning }); return {cell, signature}; } /** * @private * @param op {number} * @param hisSignature {Uint8Array} * @param cellForSigning {Cell} * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createTwoSignature(op, hisSignature, cellForSigning) { const signature = nacl.sign.detached(await cellForSigning.hash(), this.options.myKeyPair.secretKey); const signatureA = this.options.isA ? signature : hisSignature; const signatureB = !this.options.isA ? signature : hisSignature; const cell = createTwoSignature({ op, signatureA, signatureB, cell: cellForSigning }); return {cell, signature}; } /** * @param params {{coinsA: BN, coinsB: BN}} * @returns {Promise<Cell>} */ async createTopUpBalance(params) { return createTopUpBalance(params); } /** * @param params {{balanceA: BN, balanceB: BN}} * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createInitChannel(params) { return this.createOneSignature( op_init_channel, createInitChannelBody({...params, channelId: this.options.channelId}) ); } /** * @param params {{hisSignature?: Uint8Array, balanceA: BN, balanceB: BN, seqnoA: BN, seqnoB: BN}} * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createCooperativeCloseChannel(params) { if (!params.hisSignature) { params.hisSignature = new Uint8Array(512 / 8); } return this.createTwoSignature( op_cooperative_close, params.hisSignature, createCooperativeCloseChannelBody({...params, channelId: this.options.channelId}) ) } /** * @param params {{hisSignature?: Uint8Array, seqnoA: BN, seqnoB: BN}} * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createCooperativeCommit(params) { if (!params.hisSignature) { params.hisSignature = new Uint8Array(512 / 8); } return this.createTwoSignature( op_cooperative_commit, params.hisSignature, createCooperativeCommitBody({...params, channelId: this.options.channelId}) ) } /** * @private * @param params {{mySeqno: BN, mySentCoins: BN, hisSeqno?: BN, hisSentCoins?: BN}} * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createSignedSemiChannelState(params) { const state = createSemiChannelState({ channelId: this.options.channelId, semiChannelBody: createSemiChannelBody({ seqno: params.mySeqno, sentCoins: params.mySentCoins, conditionals: null }), counterpartySemiChannelBody: params.hisSeqno === undefined ? null : createSemiChannelBody({ seqno: params.hisSeqno, sentCoins: params.hisSentCoins, conditionals: null }), }); const signature = nacl.sign.detached(await state.hash(), this.options.myKeyPair.secretKey); const cell = createSignedSemiChannelState({signature, state}); return {cell, signature}; } /** * @param params {{balanceA: BN, balanceB: BN, seqnoA: BN, seqnoB: BN}} * @returns {Promise<Uint8Array>} signature */ async signState(params) { const mySeqno = this.options.isA ? params.seqnoA : params.seqnoB; const hisSeqno = !this.options.isA ? params.seqnoA : params.seqnoB; const sentCoinsA = this.options.initBalanceA.gt(params.balanceA) ? this.options.initBalanceA.sub(params.balanceA) : new BN(0); const sentCoinsB = this.options.initBalanceB.gt(params.balanceB) ? this.options.initBalanceB.sub(params.balanceB) : new BN(0); const mySentCoins = this.options.isA ? sentCoinsA : sentCoinsB; const hisSentCoins = !this.options.isA ? sentCoinsA : sentCoinsB; const {cell, signature} = await this.createSignedSemiChannelState({ mySeqno, mySentCoins, hisSeqno, hisSentCoins }); return signature; } /** * @param params {{balanceA: BN, balanceB: BN, seqnoA: BN, seqnoB: BN}} * @param hisSignature {Uint8Array} * @returns {Promise<boolean>} */ async verifyState(params, hisSignature) { const mySeqno = !this.options.isA ? params.seqnoA : params.seqnoB; const hisSeqno = this.options.isA ? params.seqnoA : params.seqnoB; const sentCoinsA = this.options.initBalanceA.gt(params.balanceA) ? this.options.initBalanceA.sub(params.balanceA) : new BN(0); const sentCoinsB = this.options.initBalanceB.gt(params.balanceB) ? this.options.initBalanceB.sub(params.balanceB) : new BN(0); const mySentCoins = !this.options.isA ? sentCoinsA : sentCoinsB; const hisSentCoins = this.options.isA ? sentCoinsA : sentCoinsB; const state = createSemiChannelState({ channelId: this.options.channelId, semiChannelBody: createSemiChannelBody({ seqno: mySeqno, sentCoins: mySentCoins, conditionals: null }), counterpartySemiChannelBody: hisSeqno === undefined ? null : createSemiChannelBody({ seqno: hisSeqno, sentCoins: hisSentCoins, conditionals: null }), }); const hash = await state.hash(); return nacl.sign.detached.verify(hash, hisSignature, this.options.isA ? this.options.publicKeyB : this.options.publicKeyA); } /** * @param params {{balanceA: BN, balanceB: BN, seqnoA: BN, seqnoB: BN}} * @return {Uint8Array} signature */ async signClose(params) { const {cell, signature} = await this.createCooperativeCloseChannel(params); return signature; } /** * @param params {{balanceA: BN, balanceB: BN, seqnoA: BN, seqnoB: BN}} * @param hisSignature {Uint8Array} * @return {boolean} */ async verifyClose(params, hisSignature) { const cell = await createCooperativeCloseChannelBody({...params, channelId: this.options.channelId}); const hash = await cell.hash(); return nacl.sign.detached.verify(hash, hisSignature, this.options.isA ? this.options.publicKeyB : this.options.publicKeyA); } /** * @param params {{signedSemiChannelStateA: Cell, signedSemiChannelStateB: Cell}} `signedSemiChannelState` created by `createSignedSemiChannelState` * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createStartUncooperativeClose(params) { return this.createOneSignature( op_start_uncooperative_close, createStartUncooperativeCloseBody({...params, channelId: this.options.channelId}) ); } /** * @param params {{signedSemiChannelStateA: Cell, signedSemiChannelStateB: Cell}} `signedSemiChannelState` created by `createSignedSemiChannelState` * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createChallengeQuarantinedState(params) { return this.createOneSignature( op_challenge_quarantined_state, createChallengeQuarantinedStateBody({...params, channelId: this.options.channelId}) ); } /** * @param params {{conditionalsToSettle: Cell | null}} dictionary with uint32 keys and values created by `createConditionalPayment` * @returns {Promise<{cell: Cell, signature: Uint8Array}>} */ async createSettleConditionals(params) { return this.createOneSignature( op_settle_conditionals, createSettleConditionalsBody({...params, channelId: this.options.channelId}) ); } /** * @returns {Promise<Cell>} */ async createFinishUncooperativeClose() { return createFinishUncooperativeClose(); } static STATE_UNINITED = 0; static STATE_OPEN = 1; static STATE_CLOSURE_STARTED = 2; static STATE_SETTLING_CONDITIONALS = 3; static STATE_AWAITING_FINALIZATION = 4; /** * @returns {Promise<number>} */ async getChannelState() { const myAddress = await this.getAddress(); const result = await this.provider.call2(myAddress.toString(), 'get_channel_state', []); return result.toNumber(); } /** * @returns {Promise<{state: number, balanceA: BN, balanceB: BN, publicKeyA: Uint8Array, publicKeyB: Uint8Array, channelId: BN, quarantineDuration: number, misbehaviorFine: BN, conditionalCloseDuration: number, seqnoA: BN, seqnoB: BN, quarantine: Cell, excessFee: BN, addressA: Address, addressB: Address}>} */ async getData() { /** * @param bn {BN} * @return {Uint8Array} */ const bnToBytes = (bn) => { let hex = bn.toString(16); if (hex.length % 2 !== 0) hex = '0' + hex; return hexToBytes(hex); } const myAddress = await this.getAddress(); const result = await this.provider.call2(myAddress.toString(), 'get_channel_data', []); const state = result[0].toNumber(); const balanceA = result[1][0]; const balanceB = result[1][1]; const publicKeyA = bnToBytes(result[2][0]); const publicKeyB = bnToBytes(result[2][1]); const channelId = result[3]; const quarantineDuration = result[4][0].toNumber(); const misbehaviorFine = result[4][1]; const conditionalCloseDuration = result[4][2].toNumber(); const seqnoA = result[5][0]; const seqnoB = result[5][1]; const quarantine = result[6]; // Cell const excessFee = result[7][0]; const addressA = parseAddress(result[7][1]); const addressB = parseAddress(result[7][2]); return { state, balanceA, balanceB, publicKeyA, publicKeyB, channelId, quarantineDuration, misbehaviorFine, conditionalCloseDuration, seqnoA, seqnoB, quarantine, excessFee, addressA, addressB } } /** * @param params {{wallet: WalletContract, secretKey: Uint8Array}} * @return {{deploy: Function, init: Function, topUp: Function, close: Function, commit: Function, startUncooperativeClose: Function, challengeQuarantinedState: Function, settleConditionals: Function, finishUncooperativeClose: Function}} */ fromWallet(params) { const {wallet, secretKey} = params; const transfer = (payloadPromise, needStateInit) => { const createPromise = async (amount) => { const stateInit = needStateInit ? (await this.createStateInit()).stateInit : null const myAddress = await this.getAddress(); const seqno = (await wallet.methods.seqno().call()) || 0; const payload = await payloadPromise; return wallet.methods.transfer({ secretKey: secretKey, toAddress: myAddress.toString(true, true, true), amount: amount, seqno: seqno, payload, // body stateInit, sendMode: 3 }); } return { /** * @param amount {BN} in Toncoins */ send: (amount) => { return createPromise(amount).then(x => x.send()); }, /** * @param amount {BN} in Toncoins */ estimateFee: (amount) => { return createPromise(amount).then(x => x.estimateFee()); } } } return { deploy: () => { return transfer(null, true); }, /** * @param params {{balanceA: BN, balanceB: BN}} */ init: (params) => { return transfer(this.createInitChannel(params).then(x => x.cell)); }, /** * @param params {{coinsA: BN, coinsB: BN}} */ topUp: (params) => { return transfer(this.createTopUpBalance(params)); }, /** * @param params {{hisSignature: Uint8Array, balanceA: BN, balanceB: BN, seqnoA: BN, seqnoB: BN}} */ close: (params) => { return transfer(this.createCooperativeCloseChannel(params).then(x => x.cell)); }, /** * @param params {{hisSignature: Uint8Array, seqnoA: BN, seqnoB: BN}} */ commit: (params) => { return transfer(this.createCooperativeCommit(params).then(x => x.cell)); }, /** * @param params {{signedSemiChannelStateA: Cell, signedSemiChannelStateB: Cell}} */ startUncooperativeClose: (params) => { return transfer(this.createStartUncooperativeClose(params).then(x => x.cell)); }, /** * @param params {{signedSemiChannelStateA: Cell, signedSemiChannelStateB: Cell}} */ challengeQuarantinedState: (params) => { return transfer(this.createChallengeQuarantinedState(params).then(x => x.cell)); }, /** * @param params {{conditionalsToSettle: Cell | null}} */ settleConditionals: (params) => { return transfer(this.createSettleConditionals(params).then(x => x.cell)); }, /** */ finishUncooperativeClose: () => { return transfer(this.createFinishUncooperativeClose()); } } } } PaymentChannel.codeHex = PAYMENT_CHANNEL_CODE_HEX; module.exports = {PaymentChannel};