Skip to content

Commit

Permalink
✨ Use arweave v2 bundlr upload API for ISCN single file upload (#390)
Browse files Browse the repository at this point in the history
* ✨ Use arweave v2 bundlr upload API for ISCN single file upload

* 💩 Fix webpack build issue for bundlr

* 🔧 Increase file size limit

* ⚡️ Use base64 instead of hex for buffer

* ✨ Add ipfs tag in arweave

* ✨ Add arweave v2 register id API
  • Loading branch information
williamchong authored Sep 21, 2023
1 parent 385ecd8 commit 03ed8ae
Show file tree
Hide file tree
Showing 8 changed files with 1,674 additions and 67 deletions.
47 changes: 19 additions & 28 deletions components/IscnRegisterForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -592,13 +592,12 @@ import { namespace } from 'vuex-class'
import { AxiosResponse } from 'axios'
import { Author } from '~/types/author'
import { estimateBundlrFilePrice, uploadSingleFileToBundlr } from '~/utils/arweave/v2'
import { signISCNTx } from '~/utils/cosmos/iscn';
import { DEFAULT_TRANSFER_FEE, sendLIKE } from '~/utils/cosmos/sign';
import { estimateISCNTxGasAndFee, formatISCNTxPayload } from '~/utils/cosmos/iscn/sign';
import {
getLikerIdMinApi,
API_POST_ARWEAVE_ESTIMATE,
API_POST_ARWEAVE_UPLOAD,
API_POST_NUMBERS_PROTOCOL_ASSETS,
} from '~/constant/api';
import { getAccountBalance } from '~/utils/cosmos'
Expand Down Expand Up @@ -790,6 +789,10 @@ export default class IscnRegisterForm extends Vue {
return undefined
}
get fileByteSize() {
return this.fileBlob?.size || 0
}
get payload() {
return {
type: this.type,
Expand Down Expand Up @@ -1119,18 +1122,10 @@ export default class IscnRegisterForm extends Vue {
}
async estimateArweaveFee(): Promise<void> {
const formData = new FormData();
if (this.fileBlob) formData.append('file', this.fileBlob);
try {
const { address, arweaveId, LIKE } = await this.$axios.$post(
API_POST_ARWEAVE_ESTIMATE,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
);
const { address, arweaveId, LIKE } = await estimateBundlrFilePrice({
fileSize: this.fileByteSize, ipfsHash: this.ipfsHash,
})
this.uploadArweaveId = arweaveId;
if (arweaveId) {
this.$emit('arweaveUploaded', { arweaveId })
Expand All @@ -1150,7 +1145,7 @@ export default class IscnRegisterForm extends Vue {
if (!this.signer) throw new Error('SIGNER_NOT_INITED');
if (!this.arweaveFeeTargetAddress) throw new Error('TARGET_ADDRESS_NOT_SET');
this.uploadStatus = 'signing';
const memo = JSON.stringify({ ipfs: this.ipfsHash });
const memo = JSON.stringify({ ipfs: this.ipfsHash, fileSize: this.fileByteSize });
try {
const { transactionHash } = await sendLIKE(this.address, this.arweaveFeeTargetAddress, this.arweaveFee.toFixed(), this.signer, memo);
return transactionHash;
Expand All @@ -1168,30 +1163,26 @@ export default class IscnRegisterForm extends Vue {
async submitToArweave(): Promise<void> {
logTrackerEvent(this, 'ISCNCreate', 'SubmitToArweave', '', 1);
if (this.uploadArweaveId) return;
if (!this.fileBlob) return;
this.isOpenSignDialog = true;
this.onOpenKeplr();
const transactionHash = await this.sendArweaveFeeTx();
const formData = new FormData();
if (this.fileBlob) formData.append('file', this.fileBlob);
// Register Numbers Protocol assets along with Arweave
if (this.isRegisterNumbersProtocolAsset) {
logTrackerEvent(this, 'ISCNCreate', 'SubmitToNumbers', '', 1);
formData.append('num', '1')
}
this.isUploadingArweave = true;
this.uploadStatus = 'uploading';
try {
const {
arweaveId,
} = await this.$axios.$post(
`${API_POST_ARWEAVE_UPLOAD}?txHash=${transactionHash}`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
)
const arrayBuffer = await this.fileBlob.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const arweaveId = await uploadSingleFileToBundlr(buffer, {
fileSize: this.fileByteSize,
ipfsHash: this.ipfsHash,
fileType: this.fileType as string,
txHash: transactionHash,
});
if (arweaveId) {
this.uploadArweaveId = arweaveId
this.$emit('arweaveUploaded', { arweaveId })
Expand Down
4 changes: 2 additions & 2 deletions components/IscnUploadForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ import exifr from 'exifr'
import Hash from 'ipfs-only-hash'
import { Vue, Component, Prop } from 'vue-property-decorator'
import { logTrackerEvent } from '~/utils/logger'
import { IS_CHAIN_UPGRADING } from '~/constant'
import { IS_CHAIN_UPGRADING, UPLOAD_FILESIZE_MAX } from '~/constant'
import {
fileToArrayBuffer,
Expand Down Expand Up @@ -224,7 +224,7 @@ export default class UploadForm extends Vue {
if (files && files[0]) {
const reader = new FileReader()
if (files[0].size < 30000000) {
if (files[0].size < UPLOAD_FILESIZE_MAX) {
this.fileName = files[0].name
this.fileSize = files[0].size
this.fileType = `${files[0].type}`
Expand Down
4 changes: 4 additions & 0 deletions constant/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const LIKECOIN_CHAIN_API = IS_TESTNET ? 'https://node.testnet.like.co' : 'https:
export const LIKER_NFT_TARGET_ADDRESS = IS_TESTNET ? 'like1yney2cqn5qdrlc50yr5l53898ufdhxafqz9gxp' : 'like17m4vwrnhjmd20uu7tst7nv0kap6ee7js69jfrs';
export const API_POST_ARWEAVE_ESTIMATE = `${LIKE_CO_API_ROOT}/arweave/estimate`;
export const API_POST_ARWEAVE_UPLOAD = `${LIKE_CO_API_ROOT}/arweave/upload`;
export const API_GET_ARWEAVE_V2_PUBLIC_KEY = `${LIKE_CO_API_ROOT}/arweave/v2/public_key`;
export const API_POST_ARWEAVE_V2_ESTIMATE = `${LIKE_CO_API_ROOT}/arweave/v2/estimate`;
export const API_POST_ARWEAVE_V2_SIGN = `${LIKE_CO_API_ROOT}/arweave/v2/sign_payment_data`;
export const API_POST_ARWEAVE_V2_REGISTER = `${LIKE_CO_API_ROOT}/arweave/v2/register`;
export const API_LIKER_NFT_MINT = `${LIKE_CO_API_ROOT}/likernft/mint`;
export const API_LIKER_NFT_MINT_IMAGE = `${LIKE_CO_API_ROOT}/likernft/mint/image`;
export const API_LIKER_NFT_PURCHASE = `${LIKE_CO_API_ROOT}/likernft/purchase`;
Expand Down
2 changes: 2 additions & 0 deletions constant/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,6 @@ export const LIKECOIN_CHAIN_STAKING_ENDPOINT = IS_TESTNET
? 'https://likecoin-public-testnet-5.netlify.app/validators'
: 'https://dao.like.co/validators';

export const UPLOAD_FILESIZE_MAX = 100000000; // 100MB


16 changes: 16 additions & 0 deletions nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,28 @@ export default {
'@likecoin/iscn-js',
'@likecoin/wallet-connector',
'@walletconnect',
'@bundlr-network',
'@noble/curves',
'arbundle',
],
extend(config, ctx) {
/* eslint-disable no-param-reassign */
if (!ctx.isDev) {
config.resolve.alias['bn.js'] = path.join(__dirname, './node_modules/bn.js');
}
if (ctx.isClient) {
config.resolve.alias['arbundles/web'] = path.join(__dirname, './node_modules/arbundles/build/web/esm/webIndex');
} else {
config.externals = {
'@bundlr-network/client': '@bundlr-network/client',
}
// config.resolve.alias['arbundles/node'] = path.join(__dirname, './node_modules/arbundles/build/node/cjs');
}
config.module.rules.push({
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
});
/* eslint-enable no-param-reassign */
},
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"test": "jest"
},
"dependencies": {
"@bundlr-network/client": "^0.11.17",
"@cosmjs/proto-signing": "^0.30.1",
"@cosmjs/stargate": "^0.30.1",
"@keplr-wallet/types": "^0.11.4",
Expand Down
170 changes: 170 additions & 0 deletions utils/arweave/v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import axios from 'axios'

import { IS_TESTNET } from '~/constant'
import {
API_POST_ARWEAVE_V2_SIGN,
API_GET_ARWEAVE_V2_PUBLIC_KEY,
API_POST_ARWEAVE_V2_ESTIMATE,
API_POST_ARWEAVE_V2_REGISTER,
} from '~/constant/api'

class Provider {
pubKey: Buffer | null = null
fileSize = 0
ipfsHash = ''
txHash = ''

constructor({
publicKey,
fileSize,
ipfsHash,
txHash,
}: {
publicKey: string
fileSize: number
ipfsHash: string
txHash: string
}) {
this.pubKey = Buffer.from(publicKey, 'base64');
this.fileSize = fileSize
this.ipfsHash = ipfsHash
this.txHash = txHash
}

setLikeCoinTxInfo({
fileSize,
ipfsHash,
txHash,
}: {
fileSize: number
ipfsHash: string
txHash: string
}) {
this.fileSize = fileSize
this.ipfsHash = ipfsHash
this.txHash = txHash
}

setPublicKey(newPubKey: string) {
this.pubKey = Buffer.from(newPubKey, 'base64');
}

getPublicKey() {
return this.pubKey
}

getSigner = () => ({
getAddress: () => this.pubKey?.toString(),
_signTypedData: async (
_domain: never,
_types: never,
message: { address: string; 'Transaction hash': Uint8Array },
) => {
const convertedMsg = Buffer.from(message['Transaction hash']).toString(
'base64',
)
const res = await axios.post(API_POST_ARWEAVE_V2_SIGN, {
signatureData: convertedMsg,
fileSize: this.fileSize,
ipfsHash: this.ipfsHash,
txHash: this.txHash,
})
const { signature } = await res.data
const bSig = Buffer.from(signature, 'base64')
// pad & convert so it's in the format the signer expects to have to convert from.
const pad = Buffer.concat([
Buffer.from([0]),
Buffer.from(bSig),
]).toString(
'hex',
)
return pad
},
})

// eslint-disable-next-line no-underscore-dangle, class-methods-use-this
_ready() {}
}

let WebBundlr: any = null;

async function getProvider({
fileSize,
ipfsHash,
txHash,
}: {
fileSize: number
ipfsHash: string
txHash: string
}) {
const { data } = await axios.get(API_GET_ARWEAVE_V2_PUBLIC_KEY)
const { publicKey } = data
const provider = new Provider({ publicKey, fileSize, ipfsHash, txHash })
return provider
}

async function getBundler({
fileSize,
ipfsHash,
txHash,
}: {
fileSize: number
ipfsHash: string
txHash: string
}) {
if (!WebBundlr) {
WebBundlr = (await import('@bundlr-network/client')).default;
}
const p = await getProvider({ fileSize, ipfsHash, txHash })
const bundlr = new WebBundlr(
IS_TESTNET
? 'https://devnet.bundlr.network'
: 'https://node1.bundlr.network',
'matic',
p,
)
await bundlr.ready()
return bundlr
}

export async function estimateBundlrFilePrice({
fileSize,
ipfsHash,
}: {
fileSize: number
ipfsHash: string
}) {
const { data } = await axios.post(API_POST_ARWEAVE_V2_ESTIMATE, {
fileSize,
ipfsHash,
})
return data
}

export async function uploadSingleFileToBundlr(
file: Buffer,
{
fileType,
fileSize,
ipfsHash,
txHash,
}: { fileSize: number; fileType?: string, ipfsHash: string; txHash: string },
) {
const bundler = await getBundler({ fileSize, ipfsHash, txHash })
const tags = [
{ name: 'IPFS-Add', value: ipfsHash },
{ name: 'standard', value: 'v0.1'},
];
if (fileType) tags.push({ name: 'Content-Type', value: fileType })
const response = await bundler.upload(file, { tags })
const arweaveId = response.id;
if (arweaveId) {
await axios.post(API_POST_ARWEAVE_V2_REGISTER, {
fileSize,
ipfsHash,
txHash,
arweaveId,
});
}
return arweaveId;
}
Loading

0 comments on commit 03ed8ae

Please sign in to comment.