@bsv/message-box-client 1.4.1 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +2 -2
- package/dist/cjs/src/MessageBoxClient.js +371 -29
- package/dist/cjs/src/MessageBoxClient.js.map +1 -1
- package/dist/cjs/src/PeerPayClient.js +2 -1
- package/dist/cjs/src/PeerPayClient.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/MessageBoxClient.js +369 -29
- package/dist/esm/src/MessageBoxClient.js.map +1 -1
- package/dist/esm/src/PeerPayClient.js +1 -1
- package/dist/esm/src/PeerPayClient.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/MessageBoxClient.d.ts +11 -3
- package/dist/types/src/MessageBoxClient.d.ts.map +1 -1
- package/dist/types/src/PeerPayClient.d.ts +2 -0
- package/dist/types/src/PeerPayClient.d.ts.map +1 -1
- package/dist/types/src/types/permissions.d.ts +46 -1
- package/dist/types/src/types/permissions.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/package.json +2 -2
- package/src/MessageBoxClient.ts +432 -20
- package/src/PeerPayClient.ts +3 -1
- package/src/types/permissions.ts +41 -1
|
@@ -868,6 +868,143 @@ export class MessageBoxClient {
|
|
|
868
868
|
throw new Error(`Failed to send message: ${errorMessage}`);
|
|
869
869
|
}
|
|
870
870
|
}
|
|
871
|
+
/**
|
|
872
|
+
* Multi-recipient sender. Uses the multi-quote route to:
|
|
873
|
+
* - identify blocked recipients
|
|
874
|
+
* - compute per-recipient payment
|
|
875
|
+
* Then sends to the allowed recipients with payment attached.
|
|
876
|
+
*/
|
|
877
|
+
async sendMesagetoRecepients(params, overrideHost) {
|
|
878
|
+
await this.assertInitialized();
|
|
879
|
+
const { recipients, messageBox, body, skipEncryption } = params;
|
|
880
|
+
if (!Array.isArray(recipients) || recipients.length === 0) {
|
|
881
|
+
throw new Error('You must provide at least one recipient!');
|
|
882
|
+
}
|
|
883
|
+
if (!messageBox || messageBox.trim() === '') {
|
|
884
|
+
throw new Error('You must provide a messageBox to send this message into!');
|
|
885
|
+
}
|
|
886
|
+
if (body == null || (typeof body === 'string' && body.trim().length === 0)) {
|
|
887
|
+
throw new Error('Every message must have a body!');
|
|
888
|
+
}
|
|
889
|
+
// 1) Multi-quote for all recipients
|
|
890
|
+
const quoteResponse = await this.getMessageBoxQuote({
|
|
891
|
+
recipient: recipients,
|
|
892
|
+
messageBox
|
|
893
|
+
}, overrideHost);
|
|
894
|
+
const quotesByRecipient = Array.isArray(quoteResponse?.quotesByRecipient)
|
|
895
|
+
? quoteResponse.quotesByRecipient : [];
|
|
896
|
+
const blocked = (quoteResponse?.blockedRecipients ?? []);
|
|
897
|
+
const totals = quoteResponse?.totals;
|
|
898
|
+
// 2) Filter allowed recipients
|
|
899
|
+
const allowedRecipients = recipients.filter(r => !blocked.includes(r));
|
|
900
|
+
if (allowedRecipients.length === 0) {
|
|
901
|
+
return {
|
|
902
|
+
status: 'error',
|
|
903
|
+
description: `All ${recipients.length} recipients are blocked.`,
|
|
904
|
+
sent: [],
|
|
905
|
+
blocked,
|
|
906
|
+
failed: recipients.map(r => ({ recipient: r, error: 'blocked' })),
|
|
907
|
+
totals
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
// 3) Map recipient -> fees
|
|
911
|
+
const perRecipientQuotes = new Map();
|
|
912
|
+
for (const q of quotesByRecipient) {
|
|
913
|
+
perRecipientQuotes.set(q.recipient, { recipientFee: q.recipientFee, deliveryFee: q.deliveryFee });
|
|
914
|
+
}
|
|
915
|
+
// 4) One delivery agent only (batch goes to one server)
|
|
916
|
+
const { deliveryAgentIdentityKeyByHost } = quoteResponse;
|
|
917
|
+
if (!deliveryAgentIdentityKeyByHost || Object.keys(deliveryAgentIdentityKeyByHost).length === 0) {
|
|
918
|
+
throw new Error('Missing delivery agent identity keys in quote response.');
|
|
919
|
+
}
|
|
920
|
+
if (Object.keys(deliveryAgentIdentityKeyByHost).length > 1 && !overrideHost) {
|
|
921
|
+
// To keep the single-POST invariant, we require all recipients to share a host
|
|
922
|
+
throw new Error('Recipients resolve to multiple hosts. Use overrideHost to force a single server or split by host.');
|
|
923
|
+
}
|
|
924
|
+
// pick the host to POST to
|
|
925
|
+
const finalHost = (overrideHost ?? await this.resolveHostForRecipient(allowedRecipients[0])).replace(/\/+$/, '');
|
|
926
|
+
const singleDeliveryKey = deliveryAgentIdentityKeyByHost[finalHost]
|
|
927
|
+
?? Object.values(deliveryAgentIdentityKeyByHost)[0];
|
|
928
|
+
if (!singleDeliveryKey) {
|
|
929
|
+
throw new Error('Could not determine server delivery agent identity key.');
|
|
930
|
+
}
|
|
931
|
+
// 5) Identity key (sender)
|
|
932
|
+
if (!this.myIdentityKey) {
|
|
933
|
+
const keyResult = await this.walletClient.getPublicKey({ identityKey: true }, this.originator);
|
|
934
|
+
this.myIdentityKey = keyResult.publicKey;
|
|
935
|
+
}
|
|
936
|
+
// 6) Build per-recipient messageIds (HMAC), same order as allowedRecipients
|
|
937
|
+
const messageIds = [];
|
|
938
|
+
for (const r of allowedRecipients) {
|
|
939
|
+
const hmac = await this.walletClient.createHmac({
|
|
940
|
+
data: Array.from(new TextEncoder().encode(JSON.stringify(body))),
|
|
941
|
+
protocolID: [1, 'messagebox'],
|
|
942
|
+
keyID: '1',
|
|
943
|
+
counterparty: r
|
|
944
|
+
}, this.originator);
|
|
945
|
+
const mid = Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
946
|
+
messageIds.push(mid);
|
|
947
|
+
}
|
|
948
|
+
// 7) Body: for batch route the server expects a single shared body
|
|
949
|
+
// NOTE: If you need per-recipient encryption, we must change the server payload shape.
|
|
950
|
+
let finalBody;
|
|
951
|
+
if (skipEncryption === true) {
|
|
952
|
+
finalBody = typeof body === 'string' ? body : JSON.stringify(body);
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
// safest for now: send plaintext; the recipients can decrypt payload fields client-side if needed
|
|
956
|
+
finalBody = typeof body === 'string' ? body : JSON.stringify(body);
|
|
957
|
+
}
|
|
958
|
+
// 8) ONE batch payment with server output at index 0
|
|
959
|
+
const paymentData = await this.createMessagePaymentBatch(allowedRecipients, perRecipientQuotes, singleDeliveryKey);
|
|
960
|
+
// 9) Single POST to /sendMessage with recipients[] + messageId[]
|
|
961
|
+
const requestBody = {
|
|
962
|
+
message: {
|
|
963
|
+
recipients: allowedRecipients,
|
|
964
|
+
messageBox,
|
|
965
|
+
messageId: messageIds, // aligned by index with recipients
|
|
966
|
+
body: finalBody
|
|
967
|
+
},
|
|
968
|
+
payment: paymentData
|
|
969
|
+
};
|
|
970
|
+
Logger.log('[MB CLIENT] Sending HTTP request to:', `${finalHost}/sendMessage`);
|
|
971
|
+
Logger.log('[MB CLIENT] Request Body (batch):', JSON.stringify({ ...requestBody, payment: { ...paymentData, tx: '<omitted>' } }, null, 2));
|
|
972
|
+
try {
|
|
973
|
+
const response = await this.authFetch.fetch(`${finalHost}/sendMessage`, {
|
|
974
|
+
method: 'POST',
|
|
975
|
+
headers: { 'Content-Type': 'application/json' },
|
|
976
|
+
body: JSON.stringify(requestBody)
|
|
977
|
+
});
|
|
978
|
+
const parsed = await response.json().catch(() => ({}));
|
|
979
|
+
if (!response.ok || parsed.status !== 'success') {
|
|
980
|
+
const msg = !response.ok ? `HTTP ${response.status} - ${response.statusText}` : (parsed.description ?? 'Unknown server error');
|
|
981
|
+
throw new Error(msg);
|
|
982
|
+
}
|
|
983
|
+
// server returns { results: [{ recipient, messageId }] }
|
|
984
|
+
const sent = Array.isArray(parsed.results) ? parsed.results : [];
|
|
985
|
+
const failed = []; // handled server-side now
|
|
986
|
+
const status = sent.length === allowedRecipients.length ? 'success'
|
|
987
|
+
: sent.length > 0 ? 'partial'
|
|
988
|
+
: 'error';
|
|
989
|
+
const description = status === 'success'
|
|
990
|
+
? `Sent to ${sent.length} recipients.`
|
|
991
|
+
: status === 'partial'
|
|
992
|
+
? `Sent to ${sent.length} recipients; ${allowedRecipients.length - sent.length} failed; ${blocked.length} blocked.`
|
|
993
|
+
: `Failed to send to ${allowedRecipients.length} allowed recipients. ${blocked.length} blocked.`;
|
|
994
|
+
return { status, description, sent, blocked, failed, totals };
|
|
995
|
+
}
|
|
996
|
+
catch (err) {
|
|
997
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
998
|
+
return {
|
|
999
|
+
status: 'error',
|
|
1000
|
+
description: `Batch send failed: ${msg}`,
|
|
1001
|
+
sent: [],
|
|
1002
|
+
blocked,
|
|
1003
|
+
failed: allowedRecipients.map(r => ({ recipient: r, error: msg })),
|
|
1004
|
+
totals
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
871
1008
|
/**
|
|
872
1009
|
* @method anointHost
|
|
873
1010
|
* @async
|
|
@@ -1519,31 +1656,144 @@ export class MessageBoxClient {
|
|
|
1519
1656
|
* })
|
|
1520
1657
|
*/
|
|
1521
1658
|
async getMessageBoxQuote(params, overrideHost) {
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1659
|
+
// ---------- SINGLE RECIPIENT (back-compat) ----------
|
|
1660
|
+
if (!Array.isArray(params.recipient)) {
|
|
1661
|
+
const finalHost = overrideHost ?? await this.resolveHostForRecipient(params.recipient);
|
|
1662
|
+
const queryParams = new URLSearchParams({
|
|
1663
|
+
recipient: params.recipient,
|
|
1664
|
+
messageBox: params.messageBox
|
|
1665
|
+
});
|
|
1666
|
+
Logger.log('[MB CLIENT] Getting messageBox quote (single)...');
|
|
1667
|
+
console.log("HELP IM QUOTING", `${finalHost}/permissions/quote?${queryParams.toString()}`);
|
|
1668
|
+
const response = await this.authFetch.fetch(`${finalHost}/permissions/quote?${queryParams.toString()}`, { method: 'GET' });
|
|
1669
|
+
console.log("server response from getquote]", response);
|
|
1670
|
+
if (!response.ok) {
|
|
1671
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1672
|
+
throw new Error(`Failed to get quote: HTTP ${response.status} - ${String(errorData.description) ?? response.statusText}`);
|
|
1673
|
+
}
|
|
1674
|
+
const { status, description, quote } = await response.json();
|
|
1675
|
+
if (status === 'error') {
|
|
1676
|
+
throw new Error(description ?? 'Failed to get quote');
|
|
1677
|
+
}
|
|
1678
|
+
const deliveryAgentIdentityKey = response.headers.get('x-bsv-auth-identity-key');
|
|
1679
|
+
console.log("deliveryAgentIdentityKey", deliveryAgentIdentityKey);
|
|
1680
|
+
if (deliveryAgentIdentityKey == null) {
|
|
1681
|
+
throw new Error('Failed to get quote: Delivery agent did not provide their identity key');
|
|
1682
|
+
}
|
|
1683
|
+
return {
|
|
1684
|
+
recipientFee: quote.recipientFee,
|
|
1685
|
+
deliveryFee: quote.deliveryFee,
|
|
1686
|
+
deliveryAgentIdentityKey
|
|
1687
|
+
};
|
|
1542
1688
|
}
|
|
1689
|
+
// ---------- MULTI RECIPIENTS ----------
|
|
1690
|
+
const recipients = params.recipient;
|
|
1691
|
+
if (recipients.length === 0) {
|
|
1692
|
+
throw new Error('At least one recipient is required.');
|
|
1693
|
+
}
|
|
1694
|
+
Logger.log('[MB CLIENT] Getting messageBox quotes (multi)...');
|
|
1695
|
+
console.log("[MB CLIENT] Getting messageBox quotes (multi)...");
|
|
1696
|
+
// Resolve host per recipient (unless caller forces overrideHost)
|
|
1697
|
+
// Group recipients by host so we call each overlay once.
|
|
1698
|
+
const hostGroups = new Map();
|
|
1699
|
+
for (const r of recipients) {
|
|
1700
|
+
const host = overrideHost ?? await this.resolveHostForRecipient(r);
|
|
1701
|
+
const list = hostGroups.get(host);
|
|
1702
|
+
if (list)
|
|
1703
|
+
list.push(r);
|
|
1704
|
+
else
|
|
1705
|
+
hostGroups.set(host, [r]);
|
|
1706
|
+
}
|
|
1707
|
+
const deliveryAgentIdentityKeyByHost = {};
|
|
1708
|
+
const quotesByRecipient = [];
|
|
1709
|
+
const blockedRecipients = [];
|
|
1710
|
+
let totalDeliveryFees = 0;
|
|
1711
|
+
let totalRecipientFees = 0;
|
|
1712
|
+
// Helper to fetch one host group
|
|
1713
|
+
const fetchGroup = async (host, groupRecipients) => {
|
|
1714
|
+
const qp = new URLSearchParams();
|
|
1715
|
+
for (const r of groupRecipients)
|
|
1716
|
+
qp.append('recipient', r);
|
|
1717
|
+
qp.set('messageBox', params.messageBox);
|
|
1718
|
+
const url = `${host}/permissions/quote?${qp.toString()}`;
|
|
1719
|
+
Logger.log('[MB CLIENT] Multi-quote GET:', url);
|
|
1720
|
+
const resp = await this.authFetch.fetch(url, { method: 'GET' });
|
|
1721
|
+
if (!resp.ok) {
|
|
1722
|
+
const errorData = await resp.json().catch(() => ({}));
|
|
1723
|
+
throw new Error(`Failed to get quote (host ${host}): HTTP ${resp.status} - ${String(errorData.description) ?? resp.statusText}`);
|
|
1724
|
+
}
|
|
1725
|
+
const deliveryAgentKey = resp.headers.get('x-bsv-auth-identity-key');
|
|
1726
|
+
if (!deliveryAgentKey) {
|
|
1727
|
+
throw new Error(`Failed to get quote (host ${host}): missing delivery agent identity key`);
|
|
1728
|
+
}
|
|
1729
|
+
deliveryAgentIdentityKeyByHost[host] = deliveryAgentKey;
|
|
1730
|
+
const payload = await resp.json();
|
|
1731
|
+
// Server supports both shapes. For multi we expect:
|
|
1732
|
+
// { quotesByRecipient, totals, blockedRecipients }
|
|
1733
|
+
if (Array.isArray(payload?.quotesByRecipient)) {
|
|
1734
|
+
// merge quotes
|
|
1735
|
+
for (const q of payload.quotesByRecipient) {
|
|
1736
|
+
quotesByRecipient.push({
|
|
1737
|
+
recipient: q.recipient,
|
|
1738
|
+
messageBox: q.messageBox,
|
|
1739
|
+
deliveryFee: q.deliveryFee,
|
|
1740
|
+
recipientFee: q.recipientFee,
|
|
1741
|
+
status: q.status
|
|
1742
|
+
});
|
|
1743
|
+
// aggregate client-side totals as well (in case we hit multiple hosts)
|
|
1744
|
+
totalDeliveryFees += q.deliveryFee;
|
|
1745
|
+
if (q.recipientFee === -1) {
|
|
1746
|
+
if (!blockedRecipients.includes(q.recipient))
|
|
1747
|
+
blockedRecipients.push(q.recipient);
|
|
1748
|
+
}
|
|
1749
|
+
else {
|
|
1750
|
+
totalRecipientFees += q.recipientFee;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
// Also merge server totals if present (they are per-host); we already aggregated above,
|
|
1754
|
+
// so we don’t need to use payload.totals except for sanity/logging.
|
|
1755
|
+
if (Array.isArray(payload?.blockedRecipients)) {
|
|
1756
|
+
for (const br of payload.blockedRecipients) {
|
|
1757
|
+
if (!blockedRecipients.includes(br))
|
|
1758
|
+
blockedRecipients.push(br);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
else if (payload?.quote) {
|
|
1763
|
+
// Defensive: if an overlay still returns single-quote shape for multi (shouldn’t),
|
|
1764
|
+
// we map it to each recipient in the group uniformly.
|
|
1765
|
+
for (const r of groupRecipients) {
|
|
1766
|
+
const { deliveryFee, recipientFee } = payload.quote;
|
|
1767
|
+
const status = recipientFee === -1 ? 'blocked' : recipientFee === 0 ? 'always_allow' : 'payment_required';
|
|
1768
|
+
quotesByRecipient.push({
|
|
1769
|
+
recipient: r,
|
|
1770
|
+
messageBox: params.messageBox,
|
|
1771
|
+
deliveryFee,
|
|
1772
|
+
recipientFee,
|
|
1773
|
+
status
|
|
1774
|
+
});
|
|
1775
|
+
totalDeliveryFees += deliveryFee;
|
|
1776
|
+
if (recipientFee === -1)
|
|
1777
|
+
blockedRecipients.push(r);
|
|
1778
|
+
else
|
|
1779
|
+
totalRecipientFees += recipientFee;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
else {
|
|
1783
|
+
throw new Error(`Unexpected quote response shape from host ${host}`);
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
// Run all host groups (in parallel, but you can limit if needed)
|
|
1787
|
+
await Promise.all(Array.from(hostGroups.entries()).map(([host, group]) => fetchGroup(host, group)));
|
|
1543
1788
|
return {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1789
|
+
quotesByRecipient,
|
|
1790
|
+
totals: {
|
|
1791
|
+
deliveryFees: totalDeliveryFees,
|
|
1792
|
+
recipientFees: totalRecipientFees,
|
|
1793
|
+
totalForPayableRecipients: totalDeliveryFees + totalRecipientFees
|
|
1794
|
+
},
|
|
1795
|
+
blockedRecipients,
|
|
1796
|
+
deliveryAgentIdentityKeyByHost
|
|
1547
1797
|
};
|
|
1548
1798
|
}
|
|
1549
1799
|
/**
|
|
@@ -1699,13 +1949,20 @@ export class MessageBoxClient {
|
|
|
1699
1949
|
*/
|
|
1700
1950
|
async sendNotification(recipient, body, overrideHost) {
|
|
1701
1951
|
await this.assertInitialized();
|
|
1702
|
-
//
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1952
|
+
// Single recipient → keep original flow
|
|
1953
|
+
if (!Array.isArray(recipient)) {
|
|
1954
|
+
return await this.sendMessage({
|
|
1955
|
+
recipient,
|
|
1956
|
+
messageBox: 'notifications',
|
|
1957
|
+
body,
|
|
1958
|
+
checkPermissions: true
|
|
1959
|
+
}, overrideHost);
|
|
1960
|
+
}
|
|
1961
|
+
// Multiple recipients → new flow
|
|
1962
|
+
return await this.sendMesagetoRecepients({
|
|
1963
|
+
recipients: recipient,
|
|
1706
1964
|
messageBox: 'notifications',
|
|
1707
|
-
body
|
|
1708
|
-
checkPermissions: true
|
|
1965
|
+
body
|
|
1709
1966
|
}, overrideHost);
|
|
1710
1967
|
}
|
|
1711
1968
|
/**
|
|
@@ -1850,6 +2107,7 @@ export class MessageBoxClient {
|
|
|
1850
2107
|
const derivationPrefix = Utils.toBase64(Random(32));
|
|
1851
2108
|
const derivationSuffix = Utils.toBase64(Random(32));
|
|
1852
2109
|
// Get host's derived public key
|
|
2110
|
+
console.log('delivery agent:', quote.deliveryAgentIdentityKey);
|
|
1853
2111
|
const { publicKey: derivedKeyResult } = await this.walletClient.getPublicKey({
|
|
1854
2112
|
protocolID: [2, '3241645161d8'],
|
|
1855
2113
|
keyID: `${derivationPrefix} ${derivationSuffix}`,
|
|
@@ -1930,5 +2188,87 @@ export class MessageBoxClient {
|
|
|
1930
2188
|
// labels
|
|
1931
2189
|
};
|
|
1932
2190
|
}
|
|
2191
|
+
async createMessagePaymentBatch(recipients, perRecipientQuotes,
|
|
2192
|
+
// server (delivery agent) identity key to pay the delivery fee to
|
|
2193
|
+
serverIdentityKey, description = 'MessageBox delivery payment (batch)') {
|
|
2194
|
+
const outputs = [];
|
|
2195
|
+
const createActionOutputs = [];
|
|
2196
|
+
// figure out the per-request delivery fee (take it from any quoted recipient)
|
|
2197
|
+
const deliveryFeeOnce = recipients.reduce((acc, r) => {
|
|
2198
|
+
const q = perRecipientQuotes.get(r);
|
|
2199
|
+
return q ? (acc ?? q.deliveryFee) : acc;
|
|
2200
|
+
}, undefined) ?? 0;
|
|
2201
|
+
const senderIdentityKey = await this.getIdentityKey();
|
|
2202
|
+
let outputIndex = 0;
|
|
2203
|
+
// index 0: server delivery fee (if any)
|
|
2204
|
+
if (deliveryFeeOnce > 0) {
|
|
2205
|
+
const derivationPrefix = Utils.toBase64(Random(32));
|
|
2206
|
+
const derivationSuffix = Utils.toBase64(Random(32));
|
|
2207
|
+
const { publicKey: agentDerived } = await this.walletClient.getPublicKey({
|
|
2208
|
+
protocolID: [2, '3241645161d8'],
|
|
2209
|
+
keyID: `${derivationPrefix} ${derivationSuffix}`,
|
|
2210
|
+
counterparty: serverIdentityKey
|
|
2211
|
+
}, this.originator);
|
|
2212
|
+
const lockingScript = new P2PKH().lock(PublicKey.fromString(agentDerived).toAddress()).toHex();
|
|
2213
|
+
createActionOutputs.push({
|
|
2214
|
+
satoshis: deliveryFeeOnce,
|
|
2215
|
+
lockingScript,
|
|
2216
|
+
outputDescription: 'MessageBox server delivery fee (batch)',
|
|
2217
|
+
customInstructions: JSON.stringify({
|
|
2218
|
+
derivationPrefix,
|
|
2219
|
+
derivationSuffix,
|
|
2220
|
+
recipientIdentityKey: serverIdentityKey
|
|
2221
|
+
})
|
|
2222
|
+
});
|
|
2223
|
+
outputs.push({
|
|
2224
|
+
outputIndex: outputIndex++,
|
|
2225
|
+
protocol: 'wallet payment',
|
|
2226
|
+
paymentRemittance: { derivationPrefix, derivationSuffix, senderIdentityKey }
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
// recipient outputs start at index 1 (or 0 if no delivery fee)
|
|
2230
|
+
const anyoneWallet = new ProtoWallet('anyone');
|
|
2231
|
+
const anyoneIdKey = (await anyoneWallet.getPublicKey({ identityKey: true })).publicKey;
|
|
2232
|
+
for (const r of recipients) {
|
|
2233
|
+
const q = perRecipientQuotes.get(r);
|
|
2234
|
+
if (!q || q.recipientFee <= 0)
|
|
2235
|
+
continue;
|
|
2236
|
+
const derivationPrefix = Utils.toBase64(Random(32));
|
|
2237
|
+
const derivationSuffix = Utils.toBase64(Random(32));
|
|
2238
|
+
const { publicKey: recipientDerived } = await anyoneWallet.getPublicKey({
|
|
2239
|
+
protocolID: [2, '3241645161d8'],
|
|
2240
|
+
keyID: `${derivationPrefix} ${derivationSuffix}`,
|
|
2241
|
+
counterparty: r
|
|
2242
|
+
});
|
|
2243
|
+
const lockingScript = new P2PKH().lock(PublicKey.fromString(recipientDerived).toAddress()).toHex();
|
|
2244
|
+
createActionOutputs.push({
|
|
2245
|
+
satoshis: q.recipientFee,
|
|
2246
|
+
lockingScript,
|
|
2247
|
+
outputDescription: `Recipient message fee (${r.slice(0, 8)}…)`,
|
|
2248
|
+
customInstructions: JSON.stringify({
|
|
2249
|
+
derivationPrefix,
|
|
2250
|
+
derivationSuffix,
|
|
2251
|
+
recipientIdentityKey: r
|
|
2252
|
+
})
|
|
2253
|
+
});
|
|
2254
|
+
outputs.push({
|
|
2255
|
+
outputIndex: outputIndex++,
|
|
2256
|
+
protocol: 'wallet payment',
|
|
2257
|
+
paymentRemittance: {
|
|
2258
|
+
derivationPrefix,
|
|
2259
|
+
derivationSuffix,
|
|
2260
|
+
senderIdentityKey: anyoneIdKey
|
|
2261
|
+
}
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
const { tx } = await this.walletClient.createAction({
|
|
2265
|
+
description,
|
|
2266
|
+
outputs: createActionOutputs,
|
|
2267
|
+
options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
|
|
2268
|
+
}, this.originator);
|
|
2269
|
+
if (!tx)
|
|
2270
|
+
throw new Error('Failed to create payment transaction');
|
|
2271
|
+
return { tx, outputs, description };
|
|
2272
|
+
}
|
|
1933
2273
|
}
|
|
1934
2274
|
//# sourceMappingURL=MessageBoxClient.js.map
|