-
Notifications
You must be signed in to change notification settings - Fork 110
/
tzip16-contract-abstraction.ts
184 lines (163 loc) · 5.55 KB
/
tzip16-contract-abstraction.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
import { BigMapId } from './handlers/tezos-storage-handler';
import {
BigMapAbstraction,
Context,
ContractAbstraction,
ContractProvider,
Wallet,
} from '@taquito/taquito';
import { bytesToString } from '@taquito/utils';
import { MetadataEnvelope, MetadataProviderInterface } from './metadata-provider';
import {
BigMapContractMetadataNotFoundError,
UnconfiguredContractMetadataProviderError,
UriNotFoundError,
} from './errors';
import BigNumber from 'bignumber.js';
import { Schema } from '@taquito/michelson-encoder';
import { ViewFactory } from './viewKind/viewFactory';
import { View } from './viewKind/interface';
import { ViewDefinition } from './metadata-interface';
export type MetadataContext = Context & {
metadataProvider: MetadataProviderInterface;
};
const metadataBigMapType = {
prim: 'big_map',
args: [{ prim: 'string' }, { prim: 'bytes' }],
annots: ['%metadata'],
};
export class Tzip16ContractAbstraction {
private _metadataProvider: MetadataProviderInterface;
private _metadataEnvelope?: MetadataEnvelope;
private _viewFactory = new ViewFactory();
private _metadataViewsObject: { [key: string]: () => View } = {};
constructor(
private constractAbstraction: ContractAbstraction<ContractProvider | Wallet>,
private context: MetadataContext
) {
this._metadataProvider = context.metadataProvider;
}
private async findMetadataBigMap(): Promise<BigMapAbstraction> {
const metadataBigMapId = this.constractAbstraction.schema.FindFirstInTopLevelPair<BigMapId>(
await this.context.readProvider.getStorage(this.constractAbstraction.address, 'head'),
metadataBigMapType
);
if (!metadataBigMapId || !metadataBigMapId.int) {
throw new BigMapContractMetadataNotFoundError(metadataBigMapId);
}
return new BigMapAbstraction(
new BigNumber(metadataBigMapId['int']),
new Schema(metadataBigMapType),
this.context.contract
);
}
private async getUriOrFail() {
const metadataBigMap = await this.findMetadataBigMap();
const uri = await metadataBigMap.get<string>('');
if (!uri) {
throw new UriNotFoundError();
}
return uri;
}
/**
* @description Return an object containing the metadata, the uri, an optional integrity check result and an optional sha256 hash
*/
async getMetadata() {
if (!this._metadataProvider) {
throw new UnconfiguredContractMetadataProviderError();
}
if (!this._metadataEnvelope) {
const uri = await this.getUriOrFail();
this._metadataEnvelope = await this._metadataProvider.provideMetadata(
this.constractAbstraction,
bytesToString(uri),
this.context
);
}
return this._metadataEnvelope;
}
async metadataName() {
if (!this._metadataEnvelope) {
await this.getMetadata();
}
return this._metadataEnvelope!.metadata.name;
}
async metadataDescription() {
if (!this._metadataEnvelope) await this.getMetadata();
return this._metadataEnvelope!.metadata.description;
}
async metadataVersion() {
if (!this._metadataEnvelope) await this.getMetadata();
return this._metadataEnvelope!.metadata.version;
}
async metadataLicense() {
if (!this._metadataEnvelope) await this.getMetadata();
return this._metadataEnvelope!.metadata.license;
}
async metadataAuthors() {
if (!this._metadataEnvelope) await this.getMetadata();
return this._metadataEnvelope!.metadata.authors;
}
async metadataHomepage() {
if (!this._metadataEnvelope) await this.getMetadata();
return this._metadataEnvelope!.metadata.homepage;
}
async metadataSource() {
if (!this._metadataEnvelope) await this.getMetadata();
return this._metadataEnvelope!.metadata.source;
}
async metadataInterfaces() {
if (!this._metadataEnvelope) await this.getMetadata();
return this._metadataEnvelope!.metadata.interfaces;
}
async metadataErrors() {
if (!this._metadataEnvelope) await this.getMetadata();
return this._metadataEnvelope!.metadata.errors;
}
async metadataViews() {
if (Object.keys(this._metadataViewsObject).length === 0) {
await this.initializeMetadataViewsList();
}
return this._metadataViewsObject;
}
private async initializeMetadataViewsList() {
const { metadata } = await this.getMetadata();
const metadataViews: any = {};
metadata.views?.forEach((view) => this.createViewImplementations(view, metadataViews));
this._metadataViewsObject = metadataViews;
}
private generateIndexedViewName(viewName: string, metadataViews: object) {
let i = 1;
if (viewName in metadataViews) {
while (`${viewName}${i}` in metadataViews) {
i++;
}
viewName = `${viewName}${i}`;
}
return viewName;
}
private createViewImplementations(view: ViewDefinition, metadataViews: any) {
for (const viewImplementation of view?.implementations ?? []) {
if (view.name) {
// when views have the same name, add an index at the end of the name
const viewName = this.generateIndexedViewName(view.name, metadataViews);
const metadataView = this._viewFactory.getView(
viewName,
this.context.rpc,
this.context.readProvider,
this.constractAbstraction,
viewImplementation
);
if (metadataView) {
metadataViews[viewName] = metadataView;
} else {
console.warn(
`Skipped generating ${viewName} because the view has an unsupported type: ${this._viewFactory.getImplementationType(
viewImplementation
)}`
);
}
}
}
}
}