The official docs portal can be found on atomicnft.com.
An Atomic NFT uses Arweave Transaction Meta Data to generate a Smart Contract and store a media file in a single transaction.
This unique standard makes a new type of NFT that is:
- Eco-friendly (contracts are lazily executed, and proof of work is used minimally)
- Contracts and media are intertwined and cannot be separated
- Media and contract data use a common locator
- Proofs of Real traffic provide attention rewards via Koii
- Can be bridged to any blockchain network
Go to narcissus to check out the example about how to deploy a NFT.
Contribute to atomic-nfts and make it better, here's the tutorial to make a good, clean pull request.
- First, go to atomicnft.com.
- Go to the page you want to make it better.
- Click the
Suggest edits on Github
. - Edit the file.(There's a pencil icon)
- After you finish, write the description and choose "Create a new branch". Give it a clear name.
- Click
Propose changes
.
The new branch will be created and you are done!
The standard contract for Atomic NFTs can be found here: r_ibeOTHJW8McJvivPJjHxjMwkYfAKRjs-LjAeaBcLc
The standard NFT has this common structure:
{
"owner": "ay1uavTv9SVRKVIrUhH8178ON_zjAsZo2tcm5wiC4bI",
"title": "Missing piece",
"name": "Kirthivasan",
"description": "nothing makes a room emptier than wanting someone in it.\nspread love",
"ticker": "KOINFT",
"balances": {
"ay1uavTv9SVRKVIrUhH8178ON_zjAsZo2tcm5wiC4bI": 1
},
"contentType": "image/jpeg",
"createdAt": "1626023631",
"locked":[]
}
A standard contract might look like this:
export function handle(state, action) {
const input = action.input;
const caller = action.caller;
if (input.function === "transfer") {
const target = input.target;
ContractAssert(target, `No target specified.`);
ContractAssert(caller !== target, `Invalid token transfer.`);
const qty = input.qty;
ContractAssert(qty && qty > 0, `No valid quantity specified.`);
const balances = state.balances;
ContractAssert(
caller in balances && balances[caller] >= qty,
`Caller has insufficient funds`
);
balances[caller] -= qty;
if (!(target in balances)) {
balances[target] = 0;
}
balances[target] += qty;
state.balances = balances;
return { state };
}
if (input.function === "balance") {
let target;
if (input.target) {
target = input.target;
} else {
target = caller;
}
const ticker = state.ticker;
const balances = state.balances;
ContractAssert(
typeof target === "string",
`Must specify target to retrieve balance for.`
);
return {
result: {
target,
ticker,
balance: target in balances ? balances[target] : 0
}
};
}
if (input.function === "lock") {
const delegatedOwner = input.delegatedOwner;
ContractAssert(delegatedOwner, `No target specified.`);
const qty = input.qty;
ContractAssert(qty && qty > 0, `No valid quantity specified.`);
const balances = state.balances;
ContractAssert(
caller in balances && balances[caller] >= qty,
`Caller has insufficient funds`
);
const address = input.address;
const network = input.network;
ContractAssert(address, `No target specified.`);
ContractAssert(network, `No network specified.`);
balances[caller] -= qty;
let lockedArray = state.locked;
let index = lockedArray.findIndex((e) => {
e.vaultAddress == delegatedOwner && e.lockedBy == caller;
});
if (index >= 0) {
lockedArray[index].amount += qty;
} else {
lockedArray.push({
UID: SmartWeave.transaction.id,
vaultAddress: delegatedOwner,
lockedBy: caller,
amount: qty,
address: address,
network: network
});
}
state.locked = lockedArray;
return { state };
}
// Only the vault owner can call this function
if (input.function === "unlock") {
const recipientAddress = input.recipientAddress;
ContractAssert(recipientAddress, `No target specified.`);
let qty = input.qty;
ContractAssert(qty && qty > 0, `No valid quantity specified.`);
let lockedArray = state.locked;
let index = lockedArray.findIndex((e) => {
return e.vaultAddress == caller;
});
ContractAssert(
index >= 0,
`Only vault owner can call this function and there must be some locked NFTs under the recipient address`
);
if (!state.balances[recipientAddress]) state.balances[recipientAddress] = 0;
if (lockedArray[index].amount - qty == 0) {
lockedArray.splice(index, 1);
state.balances[recipientAddress] =
Number(state.balances[recipientAddress]) + qty;
} else if (lockedArray[index].amount - qty > 0) {
lockedArray[index].amount -= qty;
state.balances[recipientAddress] =
Number(state.balances[recipientAddress]) + qty;
} else {
ContractAssert(
lockedArray[index].amount - qty >= 0,
`You cannot unlock more qty than currently locked`
);
}
state.locked = lockedArray;
return { state };
}
throw new ContractError(
`No function supplied or function not recognised: "${input.function}".`
);
}
The 'Lock' and 'Unlock' methods contain function for delegating ownership of an NFT when bridging to another chain.
The section below shows an example of a possible lock function:
export default function lock(state, action) {
const input = action.input;
const caller = action.caller;
const delegatedOwner = input.delegatedOwner;
ContractAssert(delegatedOwner, `No target specified.`);
const qty = input.qty;
ContractAssert(qty, `No quantity specified.`);
const balances = state.balances;
ContractAssert(
caller in balances && balances[caller] >= qty,
`Caller has insufficient funds`
);
balances[caller] -= qty;
if (!(delegatedOwner in balances)) {
balances[delegatedOwner] = 0;
}
balances[delegatedOwner] += qty;
const ethOwnerAddress = input.ethOwnerAddress;
ContractAssert(ethOwnerAddress, `No ethereum address specified.`);
state.ethOwnerAddress = ethOwnerAddress;
return { state };
}
The 'Unlock' method handles removing an NFT from custodianship.
The section below shows an example of a possible unlock function:
export default function unlock(state, action) {
const input = action.input;
const balances = state.balances;
const addresses = Object.keys(balances);
for (const address of addresses) {
delete balances[address];
}
const qty = input.qty;
ContractAssert(qty, `No quantity specified.`);
const arweaveAddress = input.arweaveAddress;
ContractAssert(arweaveAddress, `No arweaveAddress specified.`);
if (!(arweaveAddress in balances)) {
balances[arweaveAddress] = 0;
}
balances[arweaveAddress] += qty;
delete state.ethOwnerAddress;
return { state };
}
For more information about bridge compatibility, contact developers@koii.network.