/
api.ts
216 lines (206 loc) · 6.33 KB
/
api.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import type { ErrorResolver } from '@metaplex-foundation/cusper'
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
} from '@solana/web3.js'
import {
AddressLabels,
GenKeypair,
GenLabeledKeypair,
} from './diagnostics/address-labels'
import {
AmmanClient,
AmmanClientOpts,
AMMAN_RELAY_URI,
ConnectedAmmanClient,
DisconnectedAmmanClient,
} from './relay'
import {
AmmanMockStorageDriver,
AmmanMockStorageDriverOptions,
} from './storage'
import { TransactionChecker } from './transactions/transaction-checker'
import {
PayerTransactionHandler,
TransactionLabelMapper,
} from './transactions/transaction-handler'
import { logDebug } from './utils/log'
/**
* Creates an Amman instance which is used to interact with address labels and
* other amman features.
* By default it connects the socket client to the running amman validator.
* This allows it to update the amman-explorer with recent transactions and addresses.
*
* NOTE: that Amman should only be instantiated once during the life time of
* your program, tests, etc.
*
* ## Example
*
* ```js
* export const amman = Amman.instance({
* knownLabels: { [PROGRAM_ADDRESS]: 'My Program' },
* log: console.log,
* })
* ```
*
*/
export class Amman {
private constructor(
/**
* Exposes the {@link AddressLabels} API to add and query labels for
* addresses of accounts and transactions.
*/
readonly addr: AddressLabels,
readonly ammanClient: AmmanClient,
readonly errorResolver?: ErrorResolver
) {}
private static _instance: Amman | undefined
/**
* Generates a keypair and returns its public key and the keypair itself as a
* Tuple.
*
* @return [publicKey, keypair ]
*/
genKeypair: GenKeypair = () => this.addr.genKeypair()
/**
* Generates a keypair, labels it and returns its public key and the keypair
* itself as a Tuple.
*
* @param label the key will be added to existing labels
* @return [publicKey, keypair ]
*/
genLabeledKeypair: GenLabeledKeypair = (label: string) =>
this.addr.genLabeledKeypair(label)
/**
* Drops the specified amount of tokens to the provided public key.
*
* @param connection to solana JSON RPC node
* @param publicKey to drop sols to
* @param sol amount of sols to drop
*
* @category utils
*/
async airdrop(connection: Connection, publicKey: PublicKey, sol = 1) {
const sig = await connection.requestAirdrop(
publicKey,
sol * LAMPORTS_PER_SOL
)
const receiverLabel = await this.addr.resolveRemoteAddress(publicKey)
const receiver = receiverLabel == null ? '' : ` -> ${receiverLabel}`
await this.addr.addLabel(`🪂 ${sol} SOL${receiver}`, sig)
const signatureResult = await connection.confirmTransaction(sig)
return { signature: sig, signatureResult }
}
/**
* Provides a {@link TransactionHandler} which uses the {@link payer} to sign transactions.
* @catetory transactions
*/
payerTransactionHandler(
connection: Connection,
payer: Keypair,
errorResolver?: ErrorResolver
) {
this.addr.addLabelIfUnknown('payer', payer.publicKey)
return new PayerTransactionHandler(
connection,
payer,
errorResolver ?? this.errorResolver
)
}
/**
* If you cannot use the {@link payerTransactionHandler} then you can use this to verify
* the outcome of your transactions.
* @catetory transactions
* @catetory asserts
*/
transactionChecker(connection: Connection, errorResolver?: ErrorResolver) {
return new TransactionChecker(
connection,
errorResolver ?? this.errorResolver
)
}
/**
* Provides a {@link AmmanMockStorageDriver} which stores uploaded data on
* the filesystem inside a tmp directory.
* The {@link MockStorageServer} initialized with the same {@link storageId}
* serves the files from there.
*
* @category storage
*/
createMockStorageDriver = (
storageId: string,
uploadRoot: string,
options?: AmmanMockStorageDriverOptions
) => AmmanMockStorageDriver.create(storageId, uploadRoot, options)
/**
* Disconnects the amman relay client and allows the app to shut down.
* Only needed if you set `{ autoUnref: false }` for the amman client opts.
*/
disconnect() {
this.ammanClient.disconnect()
logDebug('AmmanClient disconnected')
}
/**
* More force full version of disconnect.
*/
destroy() {
this.ammanClient.destroy()
logDebug('AmmanClient destoyed')
}
/**
* Creates an instance of {@link Amman}.
*
* @param args
* @param args.knownLabels label keys that do not change, i.e. `{ [PROGRM_ID]: 'My Program' }`
* @param args.log used to log labels that are added to {@link
* Amman.addresses} and information about other events
* @param args.connectClient used to determine if to connect an amman client
* if no {@link args.ammanClient} is provided; defaults to connect unless running in a CI environment
* @param args.ammanClient allows to override the client used to connect to the amman validator
* @param args.ammanClientOpts allows to specify options for the amman relay client instead
* @param args.errorResolver used to resolve a known errors
* from the program logs, see {@link https://github.com/metaplex-foundation/cusper}
* @param args.transactionLabelMapper function to replace key strings in a
* label to enhance it, i.e. with an icon
*/
static instance(
args: {
knownLabels?: Record<string, string>
log?: (msg: string) => void
ammanClient?: AmmanClient
connectClient?: boolean
ammanClientOpts?: AmmanClientOpts
errorResolver?: ErrorResolver
transactionLabelMapper?: TransactionLabelMapper
} = {}
) {
if (Amman._instance != null) {
return Amman._instance
}
const { connectClient = process.env.CI == null, ammanClientOpts } = args
const {
knownLabels = {},
log = (_) => {},
ammanClient = connectClient
? ConnectedAmmanClient.getInstance(AMMAN_RELAY_URI, ammanClientOpts)
: new DisconnectedAmmanClient(),
} = args
const addAddressLabels = AddressLabels.setInstance(
knownLabels,
log,
ammanClient
)
Amman._instance = new Amman(
addAddressLabels,
ammanClient,
args.errorResolver
)
return Amman._instance
}
/** @internal */
static get existingInstance() {
return Amman._instance
}
}